angolo di rotazione opposto

This commit is contained in:
VALLONGOL 2025-04-09 09:06:21 +02:00
parent 99befa3214
commit 2dd7c4715c
4 changed files with 255 additions and 1 deletions

42
app.py
View File

@ -19,6 +19,7 @@ import math
import sys import sys
import socket # Required for network setup import socket # Required for network setup
from typing import Optional, Tuple, Any, Dict, TYPE_CHECKING from typing import Optional, Tuple, Any, Dict, TYPE_CHECKING
import datetime
# --- Third-party imports --- # --- Third-party imports ---
import tkinter as tk import tkinter as tk
@ -50,7 +51,7 @@ from ui import ControlPanel, StatusBar, create_main_window
# image_processing functions are used by other modules, App uses ImagePipeline now # image_processing functions are used by other modules, App uses ImagePipeline now
# from image_processing import ... # from image_processing import ...
from display import DisplayManager from display import DisplayManager
from utils import put_queue, clear_queue, decimal_to_dms from utils import put_queue, clear_queue, decimal_to_dms, generate_sar_kml, launch_google_earth
from network import create_udp_socket, close_udp_socket from network import create_udp_socket, close_udp_socket
from receiver import UdpReceiver from receiver import UdpReceiver
from app_state import AppState # Centralized state from app_state import AppState # Centralized state
@ -1128,6 +1129,7 @@ class App:
# 3. Trigger Map Update (if map manager exists and geo is valid) # 3. Trigger Map Update (if map manager exists and geo is valid)
geo_info = self.state.current_sar_geo_info # Get current info geo_info = self.state.current_sar_geo_info # Get current info
is_geo_valid = geo_info and geo_info.get("valid", False)
# Check if map manager was initialized successfully # Check if map manager was initialized successfully
map_manager_active = hasattr(self, 'map_integration_manager') and self.map_integration_manager is not None map_manager_active = hasattr(self, 'map_integration_manager') and self.map_integration_manager is not None
if map_manager_active and geo_info and geo_info.get("valid", False): if map_manager_active and geo_info and geo_info.get("valid", False):
@ -1146,6 +1148,42 @@ class App:
elif not geo_info or not geo_info.get("valid", False): elif not geo_info or not geo_info.get("valid", False):
logging.debug(f"{log_prefix} Skipping map update: GeoInfo not valid.") logging.debug(f"{log_prefix} Skipping map update: GeoInfo not valid.")
if is_geo_valid and config.ENABLE_KML_GENERATION:
kml_log_prefix = "[App KML]"
logging.debug(f"{kml_log_prefix} KML generation enabled. Proceeding...")
try:
# Assicurati che la cartella di output esista
kml_dir = config.KML_OUTPUT_DIRECTORY
os.makedirs(kml_dir, exist_ok=True)
# Crea un nome file univoco (es. basato su timestamp)
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S_%f")
kml_filename = f"sar_footprint_{timestamp}.kml"
kml_output_path = os.path.join(kml_dir, kml_filename)
# Genera il KML
logging.debug(f"{kml_log_prefix} Calling generate_sar_kml for path: {kml_output_path}")
kml_success = generate_sar_kml(geo_info, kml_output_path) # Passa geo_info
if kml_success:
logging.info(f"{kml_log_prefix} KML file generated successfully: {kml_output_path}")
# Lancia Google Earth se richiesto
if config.AUTO_LAUNCH_GOOGLE_EARTH:
logging.debug(f"{kml_log_prefix} Auto-launch Google Earth enabled. Calling launch function...")
launch_google_earth(kml_output_path)
else:
logging.debug(f"{kml_log_prefix} Auto-launch Google Earth disabled.")
else:
logging.error(f"{kml_log_prefix} KML file generation failed.")
except ImportError as ie:
# Logga se manca una libreria necessaria per KML
logging.error(f"{kml_log_prefix} Cannot generate KML due to missing library: {ie}")
except Exception as e:
logging.exception(f"{kml_log_prefix} Error during KML generation/launch process:")
elif is_geo_valid and not config.ENABLE_KML_GENERATION:
logging.debug(f"{log_prefix} KML generation disabled in config.")
# 4. Update FPS Statistics for SAR # 4. Update FPS Statistics for SAR
self._update_fps_stats("sar") # Updates self.state counters self._update_fps_stats("sar") # Updates self.state counters
@ -1795,6 +1833,8 @@ class App:
ref_lon_rad = geo["lon"] # radians ref_lon_rad = geo["lon"] # radians
orient_rad = geo.get("orientation", 0.0) # radians (default to 0 if missing) orient_rad = geo.get("orientation", 0.0) # radians (default to 0 if missing)
orient_rad = -orient_rad #inverse angle
# --- Coordinate Transformation --- # --- Coordinate Transformation ---
# 1. Normalize display coordinates to [0, 1] range # 1. Normalize display coordinates to [0, 1] range
# Avoid division by zero checked by is_geo_valid_for_calc # Avoid division by zero checked by is_geo_valid_for_calc

View File

@ -171,5 +171,12 @@ SAR_IMAGE_SIZE_KM = (
50.0 # Example: Width/Height of the area to show on the map in Kilometers 50.0 # Example: Width/Height of the area to show on the map in Kilometers
) )
# --- KML / Google Earth Integration Configuration ---
ENABLE_KML_GENERATION = True # Imposta a True per generare file KML quando arrivano dati SAR validi
KML_OUTPUT_DIRECTORY = "kml_output" # Cartella dove salvare i file KML generati
AUTO_LAUNCH_GOOGLE_EARTH = False # Imposta a True per tentare di aprire automaticamente il KML generato con Google Earth Pro (se installato)
# Opzionale: potresti aggiungere un percorso esplicito all'eseguibile di Google Earth se non è nel PATH
# GOOGLE_EARTH_EXECUTABLE_PATH = "C:/Program Files/Google/Google Earth Pro/client/googleearth.exe" # Esempio Windows
# --- END OF FILE config.py --- # --- END OF FILE config.py ---

View File

@ -125,6 +125,8 @@ class ImagePipeline:
is_geo_valid = geo_info.get("valid", False) if geo_info else False is_geo_valid = geo_info.get("valid", False) if geo_info else False
orient_rad = geo_info.get("orientation", 0.0) if is_geo_valid else 0.0 orient_rad = geo_info.get("orientation", 0.0) if is_geo_valid else 0.0
orient_rad = -orient_rad # invert angle
# 1. Apply B/C LUT (from state) # 1. Apply B/C LUT (from state)
logging.debug(f"{log_prefix} Applying B/C LUT...") logging.debug(f"{log_prefix} Applying B/C LUT...")
img = cv2.LUT(base_img, bc_lut) img = cv2.LUT(base_img, bc_lut)

205
utils.py
View File

@ -13,6 +13,28 @@ Uses standardized logging prefixes. Drop counts are now managed within AppState.
import queue import queue
import logging import logging
import math import math
import os # Aggiunto
import datetime # Aggiunto per timestamp
import sys # Aggiunto per platform check
import subprocess # Aggiunto per lanciare processi
import shutil # Aggiunto per trovare eseguibili (opzionale)
# Importa le librerie KML e GEO, gestendo l'ImportError
try:
import simplekml
_simplekml_available = True
except ImportError:
simplekml = None
_simplekml_available = False
logging.warning("[Utils KML] Library 'simplekml' not found. KML generation disabled. (pip install simplekml)")
try:
import pyproj
_pyproj_available = True
except ImportError:
pyproj = None
_pyproj_available = False
logging.warning("[Utils KML] Library 'pyproj' not found. KML generation requires it for corner calculation. (pip install pyproj)")
# Removed: threading (Lock is now in AppState) # Removed: threading (Lock is now in AppState)
@ -194,5 +216,188 @@ def decimal_to_dms(decimal_degrees, is_latitude):
) )
return "Error DMS" # Return specific error string return "Error DMS" # Return specific error string
def _calculate_geo_corners_for_kml(geo_info_radians):
"""
Helper interno per calcolare i corner geografici (gradi) da geo_info (radianti).
Basato sulla logica di MapIntegrationManager._calculate_sar_corners_geo.
Richiede pyproj.
Restituisce lista di tuple (lon, lat) in gradi o None.
"""
if not _pyproj_available: return None
log_prefix = "[Utils KML Calc]"
try:
geod = pyproj.Geod(ellps="WGS84")
# Estrai dati necessari (gestisci KeyError)
center_lat_rad = geo_info_radians['lat']
center_lon_rad = geo_info_radians['lon']
orient_rad = geo_info_radians['orientation']
ref_x = geo_info_radians['ref_x']
ref_y = geo_info_radians['ref_y']
scale_x = geo_info_radians['scale_x']
scale_y = geo_info_radians['scale_y']
width = geo_info_radians['width_px']
height = geo_info_radians['height_px']
orient_rad = -orient_rad #inverse angle
if not (scale_x > 0 and scale_y > 0 and width > 0 and height > 0):
logging.error(f"{log_prefix} Invalid scale/dimensions in geo_info.")
return None
# Calcola offset pixel e metri
corners_pixel = [
(0 - ref_x, ref_y - 0),
(width - 1 - ref_x, ref_y - 0),
(width - 1 - ref_x, ref_y - (height - 1)),
(0 - ref_x, ref_y - (height - 1))
]
corners_meters = [(dx * scale_x, dy * scale_y) for dx, dy in corners_pixel]
# Applica rotazione
corners_meters_rotated = []
if abs(orient_rad) > 1e-6:
cos_o = math.cos(orient_rad)
sin_o = math.sin(orient_rad)
for dx_m, dy_m in corners_meters:
rot_dx = dx_m * cos_o - dy_m * sin_o
rot_dy = dx_m * sin_o + dy_m * cos_o
corners_meters_rotated.append((rot_dx, rot_dy))
else:
corners_meters_rotated = corners_meters
# Calcola coordinate geografiche finali
sar_corners_geo_deg = []
center_lon_deg = math.degrees(center_lon_rad)
center_lat_deg = math.degrees(center_lat_rad)
for dx_m_rot, dy_m_rot in corners_meters_rotated:
distance_m = math.sqrt(dx_m_rot**2 + dy_m_rot**2)
azimuth_rad = math.atan2(dx_m_rot, dy_m_rot)
azimuth_deg = math.degrees(azimuth_rad)
endlon, endlat, _ = geod.fwd(center_lon_deg, center_lat_deg, azimuth_deg, distance_m)
sar_corners_geo_deg.append((endlon, endlat)) # (lon, lat)
if len(sar_corners_geo_deg) == 4:
return sar_corners_geo_deg
else:
logging.error(f"{log_prefix} Failed to calculate all 4 corner coordinates.")
return None
except KeyError as ke:
logging.error(f"{log_prefix} Missing required key in geo_info_radians: {ke}")
return None
except Exception as e:
logging.exception(f"{log_prefix} Error calculating geographic corners for KML:")
return None
def generate_sar_kml(geo_info_radians, output_path) -> bool:
"""
Genera un file KML rappresentante l'area SAR.
Args:
geo_info_radians (dict): Dizionario GeoInfo con valori in radianti.
output_path (str): Percorso completo dove salvare il file .kml.
Returns:
bool: True se il KML è stato generato e salvato, False altrimenti.
"""
log_prefix = "[Utils KML Gen]"
if not _simplekml_available or not _pyproj_available:
logging.error(f"{log_prefix} Cannot generate KML: simplekml or pyproj library missing.")
return False
if not geo_info_radians or not geo_info_radians.get("valid", False):
logging.warning(f"{log_prefix} Cannot generate KML: Invalid or missing GeoInfo.")
return False
try:
# Calcola i corner in gradi
corners_deg = _calculate_geo_corners_for_kml(geo_info_radians)
if corners_deg is None:
logging.error(f"{log_prefix} Failed to calculate SAR corners for KML.")
return False # Errore già loggato nella funzione helper
# Estrai centro e orientamento (converti in gradi per KML)
center_lon_deg = math.degrees(geo_info_radians['lon'])
center_lat_deg = math.degrees(geo_info_radians['lat'])
orientation_deg = math.degrees(geo_info_radians['orientation']) # KML usa gradi
# Calcola dimensione approssimativa per l'altitudine della vista
width_km = (geo_info_radians.get('scale_x', 1) * geo_info_radians.get('width_px', 1)) / 1000.0
height_km = (geo_info_radians.get('scale_y', 1) * geo_info_radians.get('height_px', 1)) / 1000.0
view_altitude_m = max(width_km, height_km) * 2000 # Altitudine vista = 2 * dimensione max in metri
# Crea oggetto KML
kml = simplekml.Kml(name=f"SAR Image {datetime.datetime.now():%Y%m%d_%H%M%S}")
# Aggiungi LookAt per centrare la vista
kml.document.lookat.longitude = center_lon_deg
kml.document.lookat.latitude = center_lat_deg
kml.document.lookat.range = view_altitude_m # Distanza in metri dalla coordinata
kml.document.lookat.tilt = 45 # Angolo di vista (0=diretto verso il basso)
kml.document.lookat.heading = orientation_deg # Orientamento della camera (0=Nord)
# Aggiungi un segnaposto al centro
# placemark = kml.newpoint(name="SAR Center", coords=[(center_lon_deg, center_lat_deg)])
# Aggiungi poligono per il footprint SAR
# Nota: simplekml si aspetta [(lon,lat,alt), (lon,lat,alt), ...]
# L'altitudine è opzionale, la mettiamo a 0 rispetto al suolo.
outer_boundary = [(lon, lat, 0) for lon, lat in corners_deg]
pol = kml.newpolygon(name="SAR Footprint", outerboundaryis=outer_boundary)
pol.style.linestyle.color = simplekml.Color.red # Colore linea
pol.style.linestyle.width = 2 # Spessore linea
pol.style.polystyle.color = simplekml.Color.changealphaint(100, simplekml.Color.red) # Rosso semi-trasparente per riempimento
# Salva il file KML
logging.debug(f"{log_prefix} Saving KML to: {output_path}")
kml.save(output_path)
logging.info(f"{log_prefix} KML file saved successfully: {output_path}")
return True
except Exception as e:
logging.exception(f"{log_prefix} Error generating or saving KML file:")
return False
def launch_google_earth(kml_path):
"""
Tenta di aprire un file KML con l'applicazione predefinita del sistema
(che dovrebbe essere Google Earth Pro se installato correttamente).
Args:
kml_path (str): Percorso del file KML da aprire.
"""
log_prefix = "[Utils Launch GE]"
if not os.path.exists(kml_path):
logging.error(f"{log_prefix} Cannot launch: KML file not found at {kml_path}")
return
logging.info(f"{log_prefix} Attempting to launch default KML handler for: {kml_path}")
try:
if sys.platform == "win32":
os.startfile(kml_path) # Metodo standard Windows per aprire un file con l'app associata
elif sys.platform == "darwin": # macOS
subprocess.run(['open', kml_path], check=True)
else: # Linux e altri Unix-like
# Tenta di trovare google-earth-pro nel PATH
google_earth_cmd = shutil.which('google-earth-pro')
if google_earth_cmd:
subprocess.Popen([google_earth_cmd, kml_path])
logging.debug(f"{log_prefix} Launched using found command: {google_earth_cmd}")
else:
# Fallback: usa xdg-open che usa l'associazione MIME
logging.debug(f"{log_prefix} 'google-earth-pro' not in PATH, using 'xdg-open'...")
subprocess.run(['xdg-open', kml_path], check=True)
logging.info(f"{log_prefix} Launch command issued for {kml_path}.")
except FileNotFoundError:
# Questo può accadere su Linux se né google-earth-pro né xdg-open sono trovati
logging.error(f"{log_prefix} Launch command failed: Command not found (is Google Earth Pro installed and in PATH, or xdg-utils installed?)")
except subprocess.CalledProcessError as e:
# Errore da 'open' o 'xdg-open'
logging.error(f"{log_prefix} Error launching KML handler: {e}")
except Exception as e:
logging.exception(f"{log_prefix} Unexpected error launching Google Earth:")
# --- END OF FILE utils.py --- # --- END OF FILE utils.py ---