fix track on details gui
This commit is contained in:
parent
aed7be91a4
commit
5fd3dffeef
@ -1,16 +1,15 @@
|
|||||||
# FlightMonitor/gui/dialogs/full_flight_details_window.py
|
# FlightMonitor/gui/dialogs/full_flight_details_window.py
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
# from tkinter import messagebox # Rimosso per ora
|
|
||||||
import webbrowser
|
import webbrowser
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from typing import Optional, Dict, Any, List # Rimosso Tuple
|
from typing import Optional, Dict, Any, List
|
||||||
import time # Aggiunto per il test di live_data
|
import time
|
||||||
|
|
||||||
from ...map.map_canvas_manager import MapCanvasManager
|
from ...map.map_canvas_manager import MapCanvasManager
|
||||||
from ...map.map_utils import _is_valid_bbox_dict
|
from ...map.map_utils import _is_valid_bbox_dict
|
||||||
from ...data.common_models import CanonicalFlightState
|
from ...data.common_models import CanonicalFlightState
|
||||||
from collections import deque # Aggiunto per il type hinting e l'uso
|
from collections import deque
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from ...utils.logger import get_logger
|
from ...utils.logger import get_logger
|
||||||
@ -22,41 +21,23 @@ except ImportError:
|
|||||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s: %(message)s")
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s: %(message)s")
|
||||||
logger.warning("FullFlightDetailsWindow using fallback standard Python logger.")
|
logger.warning("FullFlightDetailsWindow using fallback standard Python logger.")
|
||||||
|
|
||||||
|
DEFAULT_SINGLE_POINT_PATCH_KM = 20.0
|
||||||
|
|
||||||
|
|
||||||
class FullFlightDetailsWindow(tk.Toplevel):
|
class FullFlightDetailsWindow(tk.Toplevel):
|
||||||
def __init__(self, parent, icao24: str, controller: Optional[Any] = None):
|
def __init__(self, parent, icao24: str, controller: Optional[Any] = None):
|
||||||
# --- INIZIO BLOCCO DI DEBUG ---
|
super().__init__(parent)
|
||||||
print(f"DEBUG PRE-SUPER: FullFlightDetailsWindow __init__ for ICAO: {icao24}. Parent type: {type(parent)}")
|
|
||||||
try:
|
# MODIFIED: Normalize ICAO24 to lowercase for internal use as key
|
||||||
super().__init__(parent) # DEVE ESSERE LA PRIMA RIGA EFFETTIVA DOPO LE STAMPE DI DEBUG INIZIALI
|
self.icao24 = icao24.lower().strip()
|
||||||
print(f"DEBUG POST-SUPER: FullFlightDetailsWindow __init__ called for ICAO: {icao24}")
|
self.title(f"Full Details - {self.icao24.upper()}") # Display title can still be uppercase
|
||||||
print(f"DEBUG POST-SUPER: Type of self: {type(self)}")
|
|
||||||
print(f"DEBUG POST-SUPER: Is self an instance of tk.Toplevel? {isinstance(self, tk.Toplevel)}")
|
|
||||||
print(f"DEBUG POST-SUPER: dir(self) contains 'tk': {'tk' in dir(self)}")
|
|
||||||
|
|
||||||
if hasattr(self, 'tk'):
|
|
||||||
print(f"DEBUG POST-SUPER: self.tk IS PRESENT. Value: {self.tk}")
|
|
||||||
print(f"DEBUG POST-SUPER: Type of self.tk: {type(self.tk)}")
|
|
||||||
else:
|
|
||||||
print(f"CRITICAL DEBUG POST-SUPER: self.tk IS MISSING!")
|
|
||||||
|
|
||||||
if hasattr(self, 'protocol'):
|
|
||||||
print(f"DEBUG POST-SUPER: self.protocol method IS available.")
|
|
||||||
else:
|
|
||||||
print(f"DEBUG POST-SUPER: self.protocol method IS NOT available (pre-title).")
|
|
||||||
|
|
||||||
except Exception as e_super_init:
|
|
||||||
print(f"CRITICAL DEBUG: Exception during or immediately after super().__init__(): {e_super_init}")
|
|
||||||
# Potrebbe essere rischioso continuare se super() fallisce, ma proviamo a vedere se gli attributi base ci sono
|
|
||||||
if not hasattr(self, 'tk'):
|
|
||||||
print(f"CRITICAL DEBUG: self.tk is still missing after super() exception.")
|
|
||||||
raise # Rilancia l'eccezione se super() fallisce catastroficamente
|
|
||||||
# --- FINE BLOCCO DI DEBUG ---
|
|
||||||
|
|
||||||
self.title(f"Full Details - {icao24.upper()}")
|
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.icao24 = icao24.upper()
|
|
||||||
|
self._initial_static_data: Optional[Dict[str, Any]] = None
|
||||||
|
self._initial_live_data: Optional[Dict[str, Any]] = None
|
||||||
|
self._initial_track_data: Optional[List[Dict[str, Any]]] = None
|
||||||
|
|
||||||
self.geometry("1000x750")
|
self.geometry("1000x750")
|
||||||
self.minsize(800, 600)
|
self.minsize(800, 600)
|
||||||
@ -70,7 +51,8 @@ class FullFlightDetailsWindow(tk.Toplevel):
|
|||||||
top_info_frame.columnconfigure(0, weight=3)
|
top_info_frame.columnconfigure(0, weight=3)
|
||||||
top_info_frame.columnconfigure(1, weight=2)
|
top_info_frame.columnconfigure(1, weight=2)
|
||||||
|
|
||||||
static_details_container = ttk.LabelFrame(top_info_frame, text=f"Aircraft Information ({self.icao24})", padding=10)
|
# Usa self.icao24.upper() per il testo del LabelFrame se vuoi visualizzarlo in maiuscolo
|
||||||
|
static_details_container = ttk.LabelFrame(top_info_frame, text=f"Aircraft Information ({self.icao24.upper()})", padding=10)
|
||||||
static_details_container.grid(row=0, column=0, sticky="nsew", padx=(0, 5), rowspan=2)
|
static_details_container.grid(row=0, column=0, sticky="nsew", padx=(0, 5), rowspan=2)
|
||||||
|
|
||||||
self.detail_labels: Dict[str, ttk.Label] = {}
|
self.detail_labels: Dict[str, ttk.Label] = {}
|
||||||
@ -83,16 +65,16 @@ class FullFlightDetailsWindow(tk.Toplevel):
|
|||||||
image_link_container.columnconfigure(0, weight=1)
|
image_link_container.columnconfigure(0, weight=1)
|
||||||
|
|
||||||
self.image_placeholder_label = ttk.Label(image_link_container, text="[ Aircraft Image Area ]", relief="groove", anchor="center", borderwidth=2, padding=10)
|
self.image_placeholder_label = ttk.Label(image_link_container, text="[ Aircraft Image Area ]", relief="groove", anchor="center", borderwidth=2, padding=10)
|
||||||
self.image_placeholder_label.grid(row=0, column=0, sticky="nsew", pady=(0, 10))
|
self.image_placeholder_label.grid(row=0, column=0, sticky="nsew", pady=(0, 5))
|
||||||
|
|
||||||
self.jetphotos_link_label = ttk.Label(image_link_container, text="View on JetPhotos", foreground="blue", cursor="hand2", anchor="center")
|
self.jetphotos_link_label = ttk.Label(image_link_container, text="View on JetPhotos", foreground="blue", cursor="hand2", anchor="center")
|
||||||
self.jetphotos_link_label.grid(row=1, column=0, sticky="ew", pady=(5, 0))
|
self.jetphotos_link_label.grid(row=1, column=0, sticky="ew", pady=(0,0))
|
||||||
self.jetphotos_link_label.bind("<Button-1>", self._open_jetphotos_link_action)
|
self.jetphotos_link_label.bind("<Button-1>", self._open_jetphotos_link_action)
|
||||||
self.current_jetphotos_url: Optional[str] = None
|
self.current_jetphotos_url: Optional[str] = None
|
||||||
self.jetphotos_link_label.grid_remove()
|
self.jetphotos_link_label.grid_remove()
|
||||||
|
|
||||||
live_data_container = ttk.LabelFrame(top_info_frame, text="Current Flight Status", padding=10)
|
live_data_container = ttk.LabelFrame(top_info_frame, text="Current Flight Status", padding=10)
|
||||||
live_data_container.grid(row=1, column=1, sticky="nsew", padx=(5, 0), pady=(5, 0))
|
live_data_container.grid(row=1, column=1, sticky="nsew", padx=(5,0), pady=(5,0))
|
||||||
self._create_live_details_layout(live_data_container)
|
self._create_live_details_layout(live_data_container)
|
||||||
|
|
||||||
bottom_track_frame_container = ttk.Frame(main_v_pane, padding=5)
|
bottom_track_frame_container = ttk.Frame(main_v_pane, padding=5)
|
||||||
@ -107,6 +89,7 @@ class FullFlightDetailsWindow(tk.Toplevel):
|
|||||||
self.track_map_canvas = tk.Canvas(self.track_map_tab, bg="gray70", highlightthickness=0)
|
self.track_map_canvas = tk.Canvas(self.track_map_tab, bg="gray70", highlightthickness=0)
|
||||||
self.track_map_canvas.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
self.track_map_canvas.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
||||||
self.detail_map_manager: Optional[MapCanvasManager] = None
|
self.detail_map_manager: Optional[MapCanvasManager] = None
|
||||||
|
self._map_init_error_displayed = False
|
||||||
|
|
||||||
self.track_table_tab = ttk.Frame(self.track_notebook)
|
self.track_table_tab = ttk.Frame(self.track_notebook)
|
||||||
self.track_notebook.add(self.track_table_tab, text="Track Data Table")
|
self.track_notebook.add(self.track_table_tab, text="Track Data Table")
|
||||||
@ -147,64 +130,48 @@ class FullFlightDetailsWindow(tk.Toplevel):
|
|||||||
self.focus_set()
|
self.focus_set()
|
||||||
|
|
||||||
self.after(100, self._initialize_detail_map_manager)
|
self.after(100, self._initialize_detail_map_manager)
|
||||||
|
self.protocol("WM_DELETE_WINDOW", self._on_closing_details_window)
|
||||||
# --- BLOCCO DI DEBUG PER PROTOCOL ---
|
|
||||||
try:
|
|
||||||
print(f"DEBUG PRE-PROTOCOL: Attempting to set protocol WM_DELETE_WINDOW...")
|
|
||||||
if hasattr(self, 'tk'):
|
|
||||||
print(f"DEBUG PRE-PROTOCOL: self.tk IS STILL PRESENT before protocol call. Value: {self.tk}")
|
|
||||||
else:
|
|
||||||
print(f"CRITICAL DEBUG PRE-PROTOCOL: self.tk IS MISSING before protocol call!")
|
|
||||||
|
|
||||||
if hasattr(self, 'protocol'):
|
|
||||||
print(f"DEBUG PRE-PROTOCOL: self.protocol method IS available before protocol call.")
|
|
||||||
self.protocol("WM_DELETE_WINDOW", self._on_closing_details_window) # Riga 614 nel log precedente
|
|
||||||
print(f"DEBUG POST-PROTOCOL: Successfully set protocol WM_DELETE_WINDOW.")
|
|
||||||
else:
|
|
||||||
print(f"CRITICAL DEBUG PRE-PROTOCOL: self.protocol method IS NOT available before protocol call!")
|
|
||||||
|
|
||||||
except AttributeError as e_protocol_attr:
|
|
||||||
print(f"ERROR DURING PROTOCOL SET (AttributeError): {e_protocol_attr}")
|
|
||||||
logger.error(f"FullFlightDetailsWindow __init__ protocol AttributeError: {e_protocol_attr}", exc_info=True) # Log con traceback
|
|
||||||
except Exception as e_protocol_gen:
|
|
||||||
print(f"ERROR DURING PROTOCOL SET (General Exception): {e_protocol_gen}")
|
|
||||||
logger.error(f"FullFlightDetailsWindow __init__ protocol General Exception: {e_protocol_gen}", exc_info=True) # Log con traceback
|
|
||||||
# --- FINE BLOCCO DI DEBUG PER PROTOCOL ---
|
|
||||||
|
|
||||||
def _initialize_detail_map_manager(self):
|
def _initialize_detail_map_manager(self):
|
||||||
# ... (invariato)
|
|
||||||
if not self.track_map_canvas.winfo_exists():
|
if not self.track_map_canvas.winfo_exists():
|
||||||
logger.warning("Detail map canvas does not exist. Cannot initialize MapCanvasManager.")
|
logger.warning(f"FullDetailsWindow ({self.icao24.upper()}): Detail map canvas does not exist. Cannot initialize MapCanvasManager.")
|
||||||
return
|
return
|
||||||
|
|
||||||
canvas_w = self.track_map_canvas.winfo_width()
|
canvas_w = self.track_map_canvas.winfo_width()
|
||||||
canvas_h = self.track_map_canvas.winfo_height()
|
canvas_h = self.track_map_canvas.winfo_height()
|
||||||
|
|
||||||
if canvas_w <= 1 or canvas_h <= 1: # Canvas non ancora dimensionato
|
if canvas_w <= 1 or canvas_h <= 1:
|
||||||
logger.info("Detail map canvas not yet sized. Retrying initialization later.")
|
logger.info(f"FullDetailsWindow ({self.icao24.upper()}): Detail map canvas not yet sized. Retrying initialization later.")
|
||||||
self.after(200, self._initialize_detail_map_manager) # Riprova
|
self.after(200, self._initialize_detail_map_manager)
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.info(f"Initializing MapCanvasManager for detail window (Canvas size: {canvas_w}x{canvas_h})")
|
logger.info(f"FullDetailsWindow ({self.icao24.upper()}): Initializing MapCanvasManager for detail window (Canvas size: {canvas_w}x{canvas_h})")
|
||||||
try:
|
try:
|
||||||
self.detail_map_manager = MapCanvasManager(
|
self.detail_map_manager = MapCanvasManager(
|
||||||
app_controller=self.controller,
|
app_controller=self.controller,
|
||||||
tk_canvas=self.track_map_canvas,
|
tk_canvas=self.track_map_canvas,
|
||||||
initial_bbox_dict=None
|
initial_bbox_dict=None,
|
||||||
|
is_detail_map=True
|
||||||
)
|
)
|
||||||
self.detail_map_manager._display_placeholder_text("Awaiting flight track data...")
|
logger.info(f"FullDetailsWindow ({self.icao24.upper()}): MapCanvasManager for detail window initialized.")
|
||||||
logger.info("MapCanvasManager for detail window initialized.")
|
|
||||||
|
if self._initial_track_data is not None or self._initial_live_data is not None:
|
||||||
|
logger.info(f"FullDetailsWindow ({self.icao24.upper()}): Map manager ready, applying initial track/live data to map.")
|
||||||
|
self._update_track_map(self._initial_track_data, self._initial_live_data)
|
||||||
|
else:
|
||||||
|
self.detail_map_manager._display_placeholder_text("Awaiting flight data...")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to initialize MapCanvasManager for detail window: {e}", exc_info=True)
|
logger.error(f"FullDetailsWindow ({self.icao24.upper()}): Failed to initialize MapCanvasManager for detail window: {e}", exc_info=True)
|
||||||
if self.track_map_canvas.winfo_exists():
|
if self.track_map_canvas.winfo_exists() and not self._map_init_error_displayed:
|
||||||
self.track_map_canvas.create_text(
|
self.track_map_canvas.create_text(
|
||||||
canvas_w / 2, canvas_h / 2,
|
canvas_w / 2, canvas_h / 2,
|
||||||
text=f"Error initializing map:\n{e}",
|
text=f"Error initializing map:\n{e}",
|
||||||
fill="red", font=("Arial", 10), justify=tk.CENTER
|
fill="red", font=("Arial", 10), justify=tk.CENTER
|
||||||
)
|
)
|
||||||
|
self._map_init_error_displayed = True
|
||||||
|
|
||||||
def _create_details_layout(self, parent_frame: ttk.LabelFrame):
|
def _create_details_layout(self, parent_frame: ttk.LabelFrame):
|
||||||
# ... (invariato)
|
|
||||||
parent_frame.columnconfigure(1, weight=1)
|
parent_frame.columnconfigure(1, weight=1)
|
||||||
parent_frame.columnconfigure(3, weight=1)
|
parent_frame.columnconfigure(3, weight=1)
|
||||||
fields = [
|
fields = [
|
||||||
@ -218,8 +185,15 @@ class FullFlightDetailsWindow(tk.Toplevel):
|
|||||||
]
|
]
|
||||||
row_idx, col_idx, max_cols_per_row = 0, 0, 2
|
row_idx, col_idx, max_cols_per_row = 0, 0, 2
|
||||||
for key, text_label in fields:
|
for key, text_label in fields:
|
||||||
lbl = ttk.Label(parent_frame, text=text_label, font="-weight bold" if key == "icao24" else None)
|
lbl_text = text_label
|
||||||
|
if key == "icao24": # Per il LabelFrame, usiamo l'ICAO normalizzato (lowercase) ma per il display lo vogliamo uppercase
|
||||||
|
lbl = ttk.Label(parent_frame, text=lbl_text, font="-weight bold")
|
||||||
|
else:
|
||||||
|
lbl = ttk.Label(parent_frame, text=lbl_text)
|
||||||
lbl.grid(row=row_idx, column=col_idx * 2, sticky=tk.W, pady=1, padx=(0, 3))
|
lbl.grid(row=row_idx, column=col_idx * 2, sticky=tk.W, pady=1, padx=(0, 3))
|
||||||
|
# Per il valore di icao24, lo prenderemo da all_data che sarà già normalizzato se proviene da CanonicalFlightState
|
||||||
|
# Ma il campo ICAO24 nei dati statici potrebbe essere case-insensitive.
|
||||||
|
# La visualizzazione lo metterà in uppercase se lo forniamo così.
|
||||||
val_lbl = ttk.Label(parent_frame, text="N/A", wraplength=180)
|
val_lbl = ttk.Label(parent_frame, text="N/A", wraplength=180)
|
||||||
val_lbl.grid(row=row_idx, column=col_idx * 2 + 1, sticky=tk.W, pady=1, padx=(0, 10))
|
val_lbl.grid(row=row_idx, column=col_idx * 2 + 1, sticky=tk.W, pady=1, padx=(0, 10))
|
||||||
self.detail_labels[key] = val_lbl
|
self.detail_labels[key] = val_lbl
|
||||||
@ -227,9 +201,7 @@ class FullFlightDetailsWindow(tk.Toplevel):
|
|||||||
if col_idx >= max_cols_per_row: col_idx, row_idx = 0, row_idx + 1
|
if col_idx >= max_cols_per_row: col_idx, row_idx = 0, row_idx + 1
|
||||||
if col_idx != 0: ttk.Frame(parent_frame).grid(row=row_idx, column=col_idx*2, columnspan=(max_cols_per_row - col_idx)*2)
|
if col_idx != 0: ttk.Frame(parent_frame).grid(row=row_idx, column=col_idx*2, columnspan=(max_cols_per_row - col_idx)*2)
|
||||||
|
|
||||||
|
|
||||||
def _create_live_details_layout(self, parent_frame: ttk.LabelFrame):
|
def _create_live_details_layout(self, parent_frame: ttk.LabelFrame):
|
||||||
# ... (invariato)
|
|
||||||
parent_frame.columnconfigure(1, weight=1)
|
parent_frame.columnconfigure(1, weight=1)
|
||||||
live_fields = [
|
live_fields = [
|
||||||
("callsign", "Callsign (Live):"), ("baro_altitude_m", "Altitude (Baro):"),
|
("callsign", "Callsign (Live):"), ("baro_altitude_m", "Altitude (Baro):"),
|
||||||
@ -252,57 +224,86 @@ class FullFlightDetailsWindow(tk.Toplevel):
|
|||||||
live_data: Optional[Dict[str, Any]],
|
live_data: Optional[Dict[str, Any]],
|
||||||
full_track_data: Optional[List[Dict[str, Any]]],
|
full_track_data: Optional[List[Dict[str, Any]]],
|
||||||
):
|
):
|
||||||
# ... (invariato)
|
|
||||||
if not self.winfo_exists(): return
|
if not self.winfo_exists(): return
|
||||||
logger.debug(f"FullDetailsWindow: Updating. Static keys: {static_data.keys() if static_data else 'None'}, Live keys: {live_data.keys() if live_data else 'None'}")
|
|
||||||
|
self._initial_static_data = static_data
|
||||||
|
self._initial_live_data = live_data
|
||||||
|
self._initial_track_data = full_track_data
|
||||||
|
|
||||||
|
logger.debug(f"FullDetailsWindow ({self.icao24.upper()}): Updating. Static: {bool(static_data)}, Live: {bool(live_data)}, Track points: {len(full_track_data) if full_track_data else 0}")
|
||||||
|
|
||||||
all_data = {}
|
all_data = {}
|
||||||
if static_data: all_data.update(static_data)
|
if static_data: all_data.update(static_data)
|
||||||
if live_data: all_data.update(live_data)
|
if live_data: # Dati live sovrascrivono quelli statici se le chiavi coincidono
|
||||||
|
# Normalizza icao24 in live_data se presente, per coerenza
|
||||||
|
if 'icao24' in live_data and isinstance(live_data['icao24'], str):
|
||||||
|
live_data_processed = live_data.copy()
|
||||||
|
live_data_processed['icao24'] = live_data_processed['icao24'].lower().strip()
|
||||||
|
all_data.update(live_data_processed)
|
||||||
|
else:
|
||||||
|
all_data.update(live_data)
|
||||||
|
|
||||||
|
|
||||||
for key, label_widget in self.detail_labels.items():
|
for key, label_widget in self.detail_labels.items():
|
||||||
if label_widget.winfo_exists():
|
if label_widget.winfo_exists():
|
||||||
value = all_data.get(key)
|
value = all_data.get(key)
|
||||||
display_text = "N/A"
|
# Caso speciale per icao24: visualizzalo sempre in uppercase
|
||||||
if value is not None and not (isinstance(value, str) and not value.strip()):
|
if key == "icao24":
|
||||||
if key in ["baro_altitude_m", "geo_altitude_m"] and isinstance(value, (float, int)): display_text = f"{value:.0f} m"
|
display_text = str(value).upper() if value else "N/A"
|
||||||
elif key == "velocity_mps" and isinstance(value, (float, int)): display_text = f"{value:.1f} m/s ({value * 1.94384:.1f} kts)"
|
else:
|
||||||
elif key == "vertical_rate_mps" and isinstance(value, (float, int)): display_text = f"{value * 196.85:.0f} ft/min ({value:.1f} m/s)"
|
display_text = "N/A"
|
||||||
elif key == "true_track_deg" and isinstance(value, (float, int)): display_text = f"{value:.1f}°"
|
if value is not None and not (isinstance(value, str) and not value.strip()):
|
||||||
elif key in ["timestamp", "last_contact_timestamp", "firstflightdate", "timestamp_metadata"]:
|
if key in ["baro_altitude_m", "geo_altitude_m"] and isinstance(value, (float, int)): display_text = f"{value:.0f} m"
|
||||||
if isinstance(value, (int, float)) and value > 0:
|
elif key == "velocity_mps" and isinstance(value, (float, int)): display_text = f"{value:.1f} m/s ({value * 1.94384:.1f} kts)"
|
||||||
try: display_text = datetime.fromtimestamp(value, tz=timezone.utc).strftime('%Y-%m-%d %H:%M:%S Z')
|
elif key == "vertical_rate_mps" and isinstance(value, (float, int)): display_text = f"{value * 196.85:.0f} ft/min ({value:.1f} m/s)"
|
||||||
except: display_text = str(value) + " (raw ts)"
|
elif key == "true_track_deg" and isinstance(value, (float, int)): display_text = f"{value:.1f}°"
|
||||||
elif isinstance(value, str) and value.strip(): display_text = value
|
elif key in ["timestamp", "last_contact_timestamp", "timestamp_metadata"]:
|
||||||
elif isinstance(value, bool): display_text = str(value)
|
if isinstance(value, (int, float)) and value > 0:
|
||||||
elif key == "built_year" and value: display_text = str(int(value)) if isinstance(value, (float, int)) and value > 0 else str(value)
|
try: display_text = datetime.fromtimestamp(value, tz=timezone.utc).strftime('%Y-%m-%d %H:%M:%S Z')
|
||||||
else: display_text = str(value)
|
except: display_text = str(value) + " (raw ts)"
|
||||||
|
elif isinstance(value, str) and value.strip(): display_text = value
|
||||||
|
elif key == "firstflightdate":
|
||||||
|
if isinstance(value, str) and value.strip(): display_text = value
|
||||||
|
elif isinstance(value, (int,float)) and value > 0 :
|
||||||
|
try: display_text = datetime.fromtimestamp(value, tz=timezone.utc).strftime('%Y-%m-%d')
|
||||||
|
except: display_text = str(value)
|
||||||
|
elif isinstance(value, bool): display_text = str(value)
|
||||||
|
elif key == "built_year" and value: display_text = str(int(value)) if isinstance(value, (float, int)) and value > 0 else str(value)
|
||||||
|
else: display_text = str(value)
|
||||||
label_widget.config(text=display_text)
|
label_widget.config(text=display_text)
|
||||||
|
|
||||||
registration = all_data.get("registration")
|
registration = all_data.get("registration")
|
||||||
if registration and str(registration).strip() and str(registration) != "N/A":
|
if registration and str(registration).strip() and str(registration) != "N/A":
|
||||||
self.current_jetphotos_url = f"https://www.jetphotos.com/photo/keyword/{str(registration).upper()}"
|
self.current_jetphotos_url = f"https://www.jetphotos.com/photo/keyword/{str(registration).upper()}"
|
||||||
self.jetphotos_link_label.config(text=f"View '{str(registration).upper()}' on JetPhotos")
|
self.jetphotos_link_label.config(text=f"View '{str(registration).upper()}' on JetPhotos")
|
||||||
self.jetphotos_link_label.grid()
|
if not self.jetphotos_link_label.winfo_ismapped(): self.jetphotos_link_label.grid()
|
||||||
else:
|
else:
|
||||||
self.current_jetphotos_url = None
|
self.current_jetphotos_url = None
|
||||||
self.jetphotos_link_label.grid_remove()
|
if self.jetphotos_link_label.winfo_ismapped(): self.jetphotos_link_label.grid_remove()
|
||||||
|
|
||||||
self._update_track_table(full_track_data)
|
self._update_track_table(full_track_data)
|
||||||
self._update_track_map(full_track_data, live_data)
|
if self.detail_map_manager and self.detail_map_manager.canvas.winfo_exists():
|
||||||
|
self._update_track_map(full_track_data, live_data)
|
||||||
|
else:
|
||||||
|
logger.info(f"FullDetailsWindow ({self.icao24.upper()}): update_details called, but detail_map_manager not ready. Map will update upon manager initialization.")
|
||||||
|
|
||||||
def _update_track_table(self, track_data_list: Optional[List[Dict[str, Any]]]):
|
def _update_track_table(self, track_data_list: Optional[List[Dict[str, Any]]]):
|
||||||
# ... (invariato)
|
|
||||||
if not self.track_table.winfo_exists(): return
|
if not self.track_table.winfo_exists(): return
|
||||||
for item in self.track_table.get_children(): self.track_table.delete(item)
|
for item in self.track_table.get_children(): self.track_table.delete(item)
|
||||||
|
|
||||||
if not track_data_list:
|
if not track_data_list:
|
||||||
logger.info("FullDetailsWindow: No track data to display in table.")
|
logger.info(f"FullDetailsWindow ({self.icao24.upper()}): No track data to display in table.")
|
||||||
return
|
return
|
||||||
logger.info(f"FullDetailsWindow: Populating track table with {len(track_data_list)} points.")
|
|
||||||
for point_dict in track_data_list:
|
logger.info(f"FullDetailsWindow ({self.icao24.upper()}): Populating track table with {len(track_data_list)} points.")
|
||||||
|
for point_dict in reversed(track_data_list):
|
||||||
values = []
|
values = []
|
||||||
for col_key in self.track_table_cols.keys():
|
for col_key in self.track_table_cols.keys():
|
||||||
raw_value = point_dict.get(col_key)
|
raw_value = point_dict.get(col_key)
|
||||||
display_value = "N/A"
|
display_value = "N/A"
|
||||||
if raw_value is not None:
|
if raw_value is not None:
|
||||||
if col_key == "timestamp":
|
if col_key == "timestamp":
|
||||||
try: display_value = datetime.fromtimestamp(float(raw_value), tz=timezone.utc).strftime('%Y-%m-%d %H:%M:%S')
|
try: display_value = datetime.fromtimestamp(float(raw_value), tz=timezone.utc).strftime('%H:%M:%S')
|
||||||
except: display_value = str(raw_value)
|
except: display_value = str(raw_value)
|
||||||
elif isinstance(raw_value, float):
|
elif isinstance(raw_value, float):
|
||||||
if col_key in ["latitude", "longitude"]: display_value = f"{raw_value:.5f}"
|
if col_key in ["latitude", "longitude"]: display_value = f"{raw_value:.5f}"
|
||||||
@ -312,118 +313,177 @@ class FullFlightDetailsWindow(tk.Toplevel):
|
|||||||
elif isinstance(raw_value, bool): display_value = str(raw_value)
|
elif isinstance(raw_value, bool): display_value = str(raw_value)
|
||||||
else: display_value = str(raw_value)
|
else: display_value = str(raw_value)
|
||||||
values.append(display_value)
|
values.append(display_value)
|
||||||
self.track_table.insert("", tk.END, values=values)
|
self.track_table.insert("", 0, values=values)
|
||||||
|
|
||||||
def _update_track_map(self, track_data_list: Optional[List[Dict[str, Any]]], live_data: Optional[Dict[str, Any]]):
|
def _update_track_map(self, track_data_list: Optional[List[Dict[str, Any]]], current_live_data: Optional[Dict[str, Any]]):
|
||||||
# ... (invariato)
|
if not self.detail_map_manager or not self.detail_map_manager.canvas.winfo_exists():
|
||||||
if not self.detail_map_manager:
|
logger.warning(f"FullDetailsWindow ({self.icao24.upper()}): _update_track_map called but DetailMapManager not ready or canvas gone.")
|
||||||
logger.warning("FullDetailsWindow: DetailMapManager not initialized. Cannot update track map.")
|
self._initial_track_data = track_data_list
|
||||||
if self.track_map_canvas.winfo_exists() and not hasattr(self, "_map_init_error_displayed"):
|
self._initial_live_data = current_live_data
|
||||||
self.track_map_canvas.create_text(self.track_map_canvas.winfo_width()/2, self.track_map_canvas.winfo_height()/2, text="Map manager for detail view\nis not available.", fill="orange", font=("Arial", 10), justify=tk.CENTER)
|
|
||||||
self._map_init_error_displayed = True
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
logger.info(f"FullDetailsWindow ({self.icao24.upper()}): Updating detail map with track and live data.")
|
||||||
self.detail_map_manager.clear_map_display()
|
self.detail_map_manager.clear_map_display()
|
||||||
if not track_data_list or not track_data_list:
|
|
||||||
logger.info("FullDetailsWindow: No track data to display on map.")
|
valid_track_points_for_bbox: List[Tuple[float, float]] = []
|
||||||
self.detail_map_manager._display_placeholder_text("No flight track data available.")
|
canonical_track_states_for_drawing: List[CanonicalFlightState] = []
|
||||||
return
|
|
||||||
logger.info(f"FullDetailsWindow: Displaying track with {len(track_data_list)} points on detail map.")
|
current_icao_normalized = self.icao24 # Già lowercase da __init__
|
||||||
canonical_track_states: List[CanonicalFlightState] = []
|
|
||||||
for point_dict in track_data_list:
|
if track_data_list:
|
||||||
try:
|
for point_dict in track_data_list:
|
||||||
state = CanonicalFlightState(
|
lat, lon = point_dict.get("latitude"), point_dict.get("longitude")
|
||||||
icao24=self.icao24, timestamp=float(point_dict.get("timestamp", 0)),
|
if lat is not None and lon is not None:
|
||||||
last_contact_timestamp=float(point_dict.get("timestamp", 0)), latitude=point_dict.get("latitude"),
|
valid_track_points_for_bbox.append((lat, lon))
|
||||||
longitude=point_dict.get("longitude"), on_ground=point_dict.get("on_ground", False), callsign=None, origin_country=None,
|
try:
|
||||||
baro_altitude_m=point_dict.get("baro_altitude_m"), geo_altitude_m=point_dict.get("geo_altitude_m"),
|
state = CanonicalFlightState(
|
||||||
velocity_mps=point_dict.get("velocity_mps"), true_track_deg=point_dict.get("true_track_deg"),
|
icao24=current_icao_normalized, # Usa l'ICAO normalizzato della finestra
|
||||||
vertical_rate_mps=point_dict.get("vertical_rate_mps")
|
timestamp=float(point_dict.get("timestamp", 0)),
|
||||||
)
|
last_contact_timestamp=float(point_dict.get("timestamp", 0)),
|
||||||
canonical_track_states.append(state)
|
latitude=lat, longitude=lon, on_ground=bool(point_dict.get("on_ground", False)),
|
||||||
except Exception as e: logger.warning(f"Could not convert track point to CanonicalFlightState: {point_dict}, Error: {e}"); continue
|
# Altri campi non sono cruciali per il disegno della traccia base
|
||||||
if not canonical_track_states:
|
baro_altitude_m=point_dict.get("baro_altitude_m"),
|
||||||
logger.warning("FullDetailsWindow: No valid canonical states created from track data.")
|
geo_altitude_m=point_dict.get("geo_altitude_m"),
|
||||||
self.detail_map_manager._display_placeholder_text("Error processing track data."); return
|
velocity_mps=point_dict.get("velocity_mps"),
|
||||||
current_flight_for_map = []
|
true_track_deg=point_dict.get("true_track_deg"),
|
||||||
if live_data and live_data.get("latitude") is not None and live_data.get("longitude") is not None:
|
vertical_rate_mps=point_dict.get("vertical_rate_mps")
|
||||||
|
)
|
||||||
|
canonical_track_states_for_drawing.append(state)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Could not convert track point to CanonicalFlightState for drawing: {point_dict}, Error: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
current_flight_for_map_drawing: List[CanonicalFlightState] = []
|
||||||
|
if current_live_data:
|
||||||
|
live_lat, live_lon = current_live_data.get("latitude"), current_live_data.get("longitude")
|
||||||
|
if live_lat is not None and live_lon is not None:
|
||||||
|
valid_track_points_for_bbox.append((live_lat, live_lon))
|
||||||
try:
|
try:
|
||||||
|
# Assicurati che l'ICAO del punto live sia normalizzato se lo usi per creare CanonicalFlightState
|
||||||
|
live_icao = current_live_data.get("icao24", current_icao_normalized)
|
||||||
|
if isinstance(live_icao, str): live_icao = live_icao.lower().strip()
|
||||||
|
else: live_icao = current_icao_normalized
|
||||||
|
|
||||||
live_state = CanonicalFlightState(
|
live_state = CanonicalFlightState(
|
||||||
icao24=live_data.get("icao24", self.icao24), callsign=live_data.get("callsign"), origin_country=live_data.get("origin_country"),
|
icao24=live_icao, # Deve corrispondere alla chiave usata per la traccia
|
||||||
timestamp=float(live_data.get("timestamp", time.time())), last_contact_timestamp=float(live_data.get("last_contact_timestamp", time.time())),
|
callsign=current_live_data.get("callsign"),
|
||||||
latitude=live_data.get("latitude"), longitude=live_data.get("longitude"), baro_altitude_m=live_data.get("baro_altitude_m"),
|
origin_country=current_live_data.get("origin_country"),
|
||||||
geo_altitude_m=live_data.get("geo_altitude_m"), on_ground=live_data.get("on_ground", False), velocity_mps=live_data.get("velocity_mps"),
|
timestamp=float(current_live_data.get("timestamp", time.time())),
|
||||||
true_track_deg=live_data.get("true_track_deg"), vertical_rate_mps=live_data.get("vertical_rate_mps"), squawk=live_data.get("squawk"),
|
last_contact_timestamp=float(current_live_data.get("last_contact_timestamp", time.time())),
|
||||||
spi=live_data.get("spi"), position_source=live_data.get("position_source")
|
latitude=live_lat, longitude=live_lon,
|
||||||
|
baro_altitude_m=current_live_data.get("baro_altitude_m"),
|
||||||
|
geo_altitude_m=current_live_data.get("geo_altitude_m"),
|
||||||
|
on_ground=bool(current_live_data.get("on_ground", False)),
|
||||||
|
velocity_mps=current_live_data.get("velocity_mps"),
|
||||||
|
true_track_deg=current_live_data.get("true_track_deg"),
|
||||||
|
vertical_rate_mps=current_live_data.get("vertical_rate_mps"),
|
||||||
|
squawk=current_live_data.get("squawk"),
|
||||||
|
spi=bool(current_live_data.get("spi")),
|
||||||
|
position_source=current_live_data.get("position_source")
|
||||||
)
|
)
|
||||||
current_flight_for_map = [live_state]
|
current_flight_for_map_drawing = [live_state]
|
||||||
except Exception as e: logger.warning(f"Could not convert live data to CanonicalFlightState: {live_data}, Error: {e}")
|
except Exception as e:
|
||||||
|
logger.warning(f"Could not convert live data to CanonicalFlightState for drawing: {current_live_data}, Error: {e}")
|
||||||
|
|
||||||
|
if not valid_track_points_for_bbox:
|
||||||
|
logger.info(f"FullDetailsWindow ({self.icao24.upper()}): No valid track points or live position to display on map.")
|
||||||
|
self.detail_map_manager._display_placeholder_text("No flight track data available to display.")
|
||||||
|
return
|
||||||
|
|
||||||
with self.detail_map_manager._map_data_lock:
|
with self.detail_map_manager._map_data_lock:
|
||||||
self.detail_map_manager.flight_tracks_gui.clear()
|
self.detail_map_manager.flight_tracks_gui.clear()
|
||||||
track_deque = deque(maxlen=len(canonical_track_states) + 5)
|
track_deque = deque(maxlen=(len(canonical_track_states_for_drawing) + 5) if canonical_track_states_for_drawing else 5)
|
||||||
for state in canonical_track_states:
|
for state in canonical_track_states_for_drawing:
|
||||||
if state.latitude is not None and state.longitude is not None and state.timestamp is not None:
|
if state.latitude is not None and state.longitude is not None and state.timestamp is not None:
|
||||||
track_deque.append((state.latitude, state.longitude, state.timestamp))
|
track_deque.append((state.latitude, state.longitude, state.timestamp))
|
||||||
if track_deque: self.detail_map_manager.flight_tracks_gui[self.icao24] = track_deque
|
if track_deque:
|
||||||
if current_flight_for_map: self.detail_map_manager._current_flights_to_display_gui = current_flight_for_map
|
# Usa self.icao24 (che è già lowercase) come chiave
|
||||||
elif canonical_track_states: self.detail_map_manager._current_flights_to_display_gui = [canonical_track_states[-1]]
|
self.detail_map_manager.flight_tracks_gui[self.icao24] = track_deque
|
||||||
else: self.detail_map_manager._current_flights_to_display_gui = []
|
|
||||||
if canonical_track_states:
|
self.detail_map_manager._current_flights_to_display_gui = current_flight_for_map_drawing
|
||||||
min_lat = min(s.latitude for s in canonical_track_states if s.latitude is not None)
|
|
||||||
max_lat = max(s.latitude for s in canonical_track_states if s.latitude is not None)
|
if len(valid_track_points_for_bbox) == 1:
|
||||||
min_lon = min(s.longitude for s in canonical_track_states if s.longitude is not None)
|
lat, lon = valid_track_points_for_bbox[0]
|
||||||
max_lon = max(s.longitude for s in canonical_track_states if s.longitude is not None)
|
logger.info(f"FullDetailsWindow ({self.icao24.upper()}): Single point track/live. Centering map with patch.")
|
||||||
padding_lat = (max_lat - min_lat) * 0.10; padding_lon = (max_lon - min_lon) * 0.10
|
self.detail_map_manager.center_map_and_fit_patch(lat, lon, patch_size_km=DEFAULT_SINGLE_POINT_PATCH_KM)
|
||||||
if padding_lat == 0: padding_lat = 0.1
|
else:
|
||||||
if padding_lon == 0: padding_lon = 0.1
|
min_lat = min(p[0] for p in valid_track_points_for_bbox)
|
||||||
track_bbox = {"lat_min": max(-90, min_lat-padding_lat), "lon_min": max(-180, min_lon-padding_lon), "lat_max": min(90, max_lat+padding_lat), "lon_max": min(180, max_lon+padding_lon)}
|
max_lat = max(p[0] for p in valid_track_points_for_bbox)
|
||||||
if track_bbox["lat_min"] >= track_bbox["lat_max"]: track_bbox["lat_max"] = track_bbox["lat_min"] + 0.01
|
min_lon = min(p[1] for p in valid_track_points_for_bbox)
|
||||||
if track_bbox["lon_min"] >= track_bbox["lon_max"]: track_bbox["lon_max"] = track_bbox["lon_min"] + 0.01
|
max_lon = max(p[1] for p in valid_track_points_for_bbox)
|
||||||
|
|
||||||
|
padding_deg = 0.05
|
||||||
|
if abs(max_lat - min_lat) < 1e-6 : padding_lat_eff = padding_deg # Se i punti sono quasi collineari verticalmente
|
||||||
|
else: padding_lat_eff = max(padding_deg, (max_lat - min_lat) * 0.15)
|
||||||
|
if abs(max_lon - min_lon) < 1e-6 : padding_lon_eff = padding_deg # Se i punti sono quasi collineari orizzontalmente
|
||||||
|
else: padding_lon_eff = max(padding_deg, (max_lon - min_lon) * 0.15)
|
||||||
|
|
||||||
|
track_bbox = {
|
||||||
|
"lat_min": max(-90.0, min_lat - padding_lat_eff),
|
||||||
|
"lon_min": max(-180.0, min_lon - padding_lon_eff),
|
||||||
|
"lat_max": min(90.0, max_lat + padding_lat_eff),
|
||||||
|
"lon_max": min(180.0, max_lon + padding_lon_eff),
|
||||||
|
}
|
||||||
|
|
||||||
|
if track_bbox["lat_min"] >= track_bbox["lat_max"]:
|
||||||
|
track_bbox["lat_max"] = track_bbox["lat_min"] + 0.01
|
||||||
|
if track_bbox["lon_min"] >= track_bbox["lon_max"]:
|
||||||
|
track_bbox["lon_max"] = track_bbox["lon_min"] + 0.01
|
||||||
|
|
||||||
if _is_valid_bbox_dict(track_bbox):
|
if _is_valid_bbox_dict(track_bbox):
|
||||||
logger.info(f"FullDetailsWindow: Requesting detail map render for BBox of track: {track_bbox}")
|
logger.info(f"FullDetailsWindow ({self.icao24.upper()}): Requesting detail map render for calculated BBox of track: {track_bbox}")
|
||||||
self.detail_map_manager._target_bbox_input_gui = track_bbox
|
|
||||||
self.detail_map_manager._request_map_render_for_bbox(track_bbox, preserve_current_zoom_if_possible=False)
|
self.detail_map_manager._request_map_render_for_bbox(track_bbox, preserve_current_zoom_if_possible=False)
|
||||||
else:
|
else:
|
||||||
logger.warning(f"FullDetailsWindow: Calculated track BBox is invalid: {track_bbox}. Using last known view.")
|
logger.warning(f"FullDetailsWindow ({self.icao24.upper()}): Calculated track BBox is invalid: {track_bbox}. Attempting fallback.")
|
||||||
if self.detail_map_manager._current_center_lat_gui and self.detail_map_manager._current_center_lon_gui and self.detail_map_manager._current_zoom_gui:
|
last_known_lat, last_known_lon = valid_track_points_for_bbox[-1]
|
||||||
self.detail_map_manager._request_map_render(self.detail_map_manager._current_center_lat_gui, self.detail_map_manager._current_center_lon_gui, self.detail_map_manager._current_zoom_gui)
|
self.detail_map_manager.center_map_and_fit_patch(last_known_lat, last_known_lon, patch_size_km=DEFAULT_SINGLE_POINT_PATCH_KM * 2)
|
||||||
else: self.detail_map_manager._display_placeholder_text("Cannot determine map view for track.")
|
|
||||||
else:
|
|
||||||
if self.detail_map_manager._current_flights_to_display_gui:
|
|
||||||
live_pt = self.detail_map_manager._current_flights_to_display_gui[0]
|
|
||||||
if live_pt.latitude and live_pt.longitude:
|
|
||||||
self.detail_map_manager.center_map_and_fit_patch(live_pt.latitude, live_pt.longitude, patch_size_km=50)
|
|
||||||
else: self.detail_map_manager._display_placeholder_text("No track data to display on map.")
|
|
||||||
|
|
||||||
def _open_jetphotos_link_action(self, event=None):
|
def _open_jetphotos_link_action(self, event=None):
|
||||||
# ... (invariato)
|
|
||||||
if self.current_jetphotos_url:
|
if self.current_jetphotos_url:
|
||||||
try: webbrowser.open_new_tab(self.current_jetphotos_url); logger.info(f"Opening JetPhotos link: {self.current_jetphotos_url}")
|
try:
|
||||||
except Exception as e: logger.error(f"Failed to open JetPhotos link {self.current_jetphotos_url}: {e}"); # messagebox.showerror("Link Error", f"Could not open link: {e}", parent=self)
|
webbrowser.open_new_tab(self.current_jetphotos_url)
|
||||||
else: logger.warning("No JetPhotos URL to open for current aircraft."); # messagebox.showinfo("No Link", "No registration found to search on JetPhotos.", parent=self)
|
logger.info(f"Opening JetPhotos link: {self.current_jetphotos_url}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to open JetPhotos link {self.current_jetphotos_url}: {e}")
|
||||||
|
else:
|
||||||
|
logger.warning("No JetPhotos URL to open for current aircraft.")
|
||||||
|
|
||||||
def center_window(self):
|
def center_window(self):
|
||||||
# ... (invariato)
|
|
||||||
self.update_idletasks()
|
self.update_idletasks()
|
||||||
width = self.winfo_width(); height = self.winfo_height()
|
width = self.winfo_width()
|
||||||
if width <= 1: width = 1000
|
height = self.winfo_height()
|
||||||
if height <= 1: height = 750
|
if width <= 1: width = self.winfo_reqwidth() if self.winfo_reqwidth() > 1 else 1000
|
||||||
x_screen = self.winfo_screenwidth(); y_screen = self.winfo_screenheight()
|
if height <= 1: height = self.winfo_reqheight() if self.winfo_reqheight() > 1 else 750
|
||||||
x = (x_screen // 2) - (width // 2); y = (y_screen // 2) - (height // 2)
|
|
||||||
if x < 0: x = 0
|
x_screen = self.winfo_screenwidth()
|
||||||
if y < 0: y = 0
|
y_screen = self.winfo_screenheight()
|
||||||
|
x = (x_screen // 2) - (width // 2)
|
||||||
|
y = (y_screen // 2) - (height // 2)
|
||||||
|
|
||||||
if x + width > x_screen: x = x_screen - width
|
if x + width > x_screen: x = x_screen - width
|
||||||
if y + height > y_screen: y = y_screen - height
|
if y + height > y_screen: y = y_screen - height
|
||||||
if width > 0 and height > 0: self.geometry(f"{width}x{height}+{x}+{y}")
|
if x < 0: x = 0
|
||||||
|
if y < 0: y = 0
|
||||||
|
|
||||||
|
if width > 0 and height > 0 : self.geometry(f"{width}x{height}+{x}+{y}")
|
||||||
|
|
||||||
def _on_closing_details_window(self):
|
def _on_closing_details_window(self):
|
||||||
logger.info(f"FullFlightDetailsWindow for {self.icao24} is closing.")
|
logger.info(f"FullFlightDetailsWindow for {self.icao24.upper()} is closing.")
|
||||||
if self.detail_map_manager:
|
if self.detail_map_manager:
|
||||||
logger.info("Requesting shutdown for detail_map_manager worker.")
|
logger.info(f"FullDetailsWindow ({self.icao24.upper()}): Requesting shutdown for detail_map_manager worker.")
|
||||||
try: self.detail_map_manager.shutdown_worker()
|
try:
|
||||||
except Exception as e: logger.error(f"Error during detail_map_manager worker shutdown: {e}", exc_info=True)
|
self.detail_map_manager.shutdown_worker()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"FullDetailsWindow ({self.icao24.upper()}): Error during detail_map_manager worker shutdown: {e}", exc_info=True)
|
||||||
|
|
||||||
|
if self.controller and hasattr(self.controller, "details_window_closed"):
|
||||||
|
try:
|
||||||
|
self.controller.details_window_closed(self.icao24) # self.icao24 è già lowercase
|
||||||
|
except Exception as e_notify_close:
|
||||||
|
logger.error(f"FullDetailsWindow ({self.icao24.upper()}): Error notifying controller of details window closure: {e_notify_close}", exc_info=True)
|
||||||
|
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
# ... (blocco if __name__ == "__main__": invariato)
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
root_test = tk.Tk()
|
root_test = tk.Tk()
|
||||||
root_test.title("Main Window (Test)")
|
root_test.title("Main Window (Test)")
|
||||||
@ -431,22 +491,42 @@ if __name__ == "__main__":
|
|||||||
"icao24": "TST001", "registration": "N-TEST", "manufacturername": "TestAircraft Co.", "model": "SkyTester Pro",
|
"icao24": "TST001", "registration": "N-TEST", "manufacturername": "TestAircraft Co.", "model": "SkyTester Pro",
|
||||||
"typecode": "TSPR", "operator": "Test Flight Ops", "built_year": 2022, "categorydescription": "Experimental Test Vehicle",
|
"typecode": "TSPR", "operator": "Test Flight Ops", "built_year": 2022, "categorydescription": "Experimental Test Vehicle",
|
||||||
"country": "Testland", "timestamp_metadata": time.time() - 3600 * 24 * 30,
|
"country": "Testland", "timestamp_metadata": time.time() - 3600 * 24 * 30,
|
||||||
|
"firstflightdate": "2022-01-15"
|
||||||
}
|
}
|
||||||
sample_live = {
|
sample_live = {
|
||||||
|
"icao24": "tst001", #Lowercase per test
|
||||||
"callsign": "TEST01", "baro_altitude_m": 10000.0, "geo_altitude_m": 10050.0, "velocity_mps": 250.5,
|
"callsign": "TEST01", "baro_altitude_m": 10000.0, "geo_altitude_m": 10050.0, "velocity_mps": 250.5,
|
||||||
"vertical_rate_mps": 5.2, "true_track_deg": 123.4, "on_ground": False, "squawk": "7000",
|
"vertical_rate_mps": 5.2, "true_track_deg": 123.4, "on_ground": False, "squawk": "7000",
|
||||||
"origin_country": "Testland Live", "timestamp": time.time() - 60,
|
"origin_country": "Testland Live", "timestamp": time.time() - 60, "last_contact_timestamp": time.time() - 58,
|
||||||
|
"latitude": 45.15, "longitude": 9.15
|
||||||
}
|
}
|
||||||
sample_track = [
|
sample_track = [
|
||||||
{"latitude": 45.0, "longitude": 9.0, "baro_altitude_m": 9000, "timestamp": time.time() - 300,},
|
{"latitude": 45.0, "longitude": 9.0, "baro_altitude_m": 9000, "timestamp": time.time() - 300, "on_ground": False},
|
||||||
{"latitude": 45.1, "longitude": 9.1, "baro_altitude_m": 9500, "timestamp": time.time() - 180,},
|
{"latitude": 45.1, "longitude": 9.1, "baro_altitude_m": 9500, "timestamp": time.time() - 180, "on_ground": False},
|
||||||
{"latitude": 45.2, "longitude": 9.2, "baro_altitude_m": 10000, "timestamp": time.time() - 60,},
|
{"latitude": 45.2, "longitude": 9.2, "baro_altitude_m": 10000, "timestamp": time.time() - 60, "on_ground": False},
|
||||||
]
|
]
|
||||||
def open_details_test():
|
sample_track_single_point = [
|
||||||
if hasattr(root_test, "details_win_instance") and root_test.details_win_instance.winfo_exists(): # type: ignore
|
{"latitude": 46.0, "longitude": 10.0, "baro_altitude_m": 8000, "timestamp": time.time() - 120, "on_ground": False}
|
||||||
root_test.details_win_instance.destroy() # type: ignore
|
]
|
||||||
details_window = FullFlightDetailsWindow(root_test, "TST001")
|
sample_live_only = {
|
||||||
details_window.update_details(sample_static, sample_live, sample_track)
|
"icao24": "tst002", "callsign": "LIVEONLY", "latitude": 45.5, "longitude": 9.5, "baro_altitude_m": 5000,
|
||||||
root_test.details_win_instance = details_window # type: ignore
|
"timestamp": time.time()-10, "last_contact_timestamp": time.time()-10, "on_ground": False
|
||||||
ttk.Button(root_test, text="Open Full Details Test", command=open_details_test).pack(padx=50, pady=50)
|
}
|
||||||
|
|
||||||
|
def open_details_test(static, live, track, icao="TST001"):
|
||||||
|
if hasattr(root_test, "details_win_instance_test") and root_test.details_win_instance_test.winfo_exists(): # type: ignore
|
||||||
|
root_test.details_win_instance_test.destroy() # type: ignore
|
||||||
|
|
||||||
|
details_window = FullFlightDetailsWindow(root_test, icao)
|
||||||
|
details_window._initial_static_data = static
|
||||||
|
details_window._initial_live_data = live
|
||||||
|
details_window._initial_track_data = track
|
||||||
|
details_window.update_details(static, live, track)
|
||||||
|
root_test.details_win_instance_test = details_window # type: ignore
|
||||||
|
|
||||||
|
ttk.Button(root_test, text="Open Full Details (Multi-Point Track)", command=lambda: open_details_test(sample_static, sample_live, sample_track, icao="tst001")).pack(padx=20, pady=5)
|
||||||
|
ttk.Button(root_test, text="Open Full Details (Single-Point Track)", command=lambda: open_details_test(sample_static, sample_live, sample_track_single_point, icao="tst001")).pack(padx=20, pady=5)
|
||||||
|
ttk.Button(root_test, text="Open Full Details (Live Only, No Track)", command=lambda: open_details_test(None, sample_live_only, None, icao="tst002")).pack(padx=20, pady=5)
|
||||||
|
ttk.Button(root_test, text="Open Full Details (No Track, No Live)", command=lambda: open_details_test(sample_static, None, None, icao="tst001")).pack(padx=20, pady=5)
|
||||||
|
|
||||||
root_test.mainloop()
|
root_test.mainloop()
|
||||||
File diff suppressed because it is too large
Load Diff
@ -740,6 +740,10 @@ def _draw_single_flight(
|
|||||||
# Determine base color for this flight (triangle and its track)
|
# Determine base color for this flight (triangle and its track)
|
||||||
color_index = hash(flight_state.icao24) % len(map_constants.TRACK_COLOR_PALETTE)
|
color_index = hash(flight_state.icao24) % len(map_constants.TRACK_COLOR_PALETTE)
|
||||||
flight_and_track_base_color_hex = map_constants.TRACK_COLOR_PALETTE[color_index]
|
flight_and_track_base_color_hex = map_constants.TRACK_COLOR_PALETTE[color_index]
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
f"MAP_DRAWING: _draw_single_flight for {flight_state.icao24} at px {pixel_coords}. Track deque length: {len(track_deque) if track_deque else 'None'}"
|
||||||
|
)
|
||||||
|
|
||||||
# --- Draw Track First (so aircraft is on top) ---
|
# --- Draw Track First (so aircraft is on top) ---
|
||||||
if track_deque and len(track_deque) > 1:
|
if track_deque and len(track_deque) > 1:
|
||||||
@ -768,6 +772,7 @@ def _draw_single_flight(
|
|||||||
|
|
||||||
# pixel_track_points are now ordered from most recent (closest to aircraft) to oldest
|
# pixel_track_points are now ordered from most recent (closest to aircraft) to oldest
|
||||||
if len(pixel_track_points) > 1:
|
if len(pixel_track_points) > 1:
|
||||||
|
logger.debug(f"MAP_DRAWING ({flight_state.icao24}): Will draw {len(pixel_track_points)-1} line segments.")
|
||||||
for i in range(len(pixel_track_points) - 1):
|
for i in range(len(pixel_track_points) - 1):
|
||||||
start_point_px = pixel_track_points[i]
|
start_point_px = pixel_track_points[i]
|
||||||
end_point_px = pixel_track_points[i + 1]
|
end_point_px = pixel_track_points[i + 1]
|
||||||
@ -775,6 +780,8 @@ def _draw_single_flight(
|
|||||||
faded_color = _calculate_faded_color(
|
faded_color = _calculate_faded_color(
|
||||||
flight_and_track_base_color_hex, i, len(pixel_track_points)
|
flight_and_track_base_color_hex, i, len(pixel_track_points)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
logger.debug(f"MAP_DRAWING ({flight_state.icao24}): Segment {i}, Start: {start_point_px}, End: {end_point_px}, Color: {faded_color}") # LOG AGGIUNTO
|
||||||
if faded_color:
|
if faded_color:
|
||||||
try:
|
try:
|
||||||
draw.line(
|
draw.line(
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user