From 2dd7c4715c687587eafe3424cde8a8bea139c9fa Mon Sep 17 00:00:00 2001 From: VALLONGOL Date: Wed, 9 Apr 2025 09:06:21 +0200 Subject: [PATCH] angolo di rotazione opposto --- app.py | 42 +++++++++- config.py | 7 ++ image_pipeline.py | 2 + utils.py | 205 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 255 insertions(+), 1 deletion(-) diff --git a/app.py b/app.py index d2168b1..4ff484a 100644 --- a/app.py +++ b/app.py @@ -19,6 +19,7 @@ import math import sys import socket # Required for network setup from typing import Optional, Tuple, Any, Dict, TYPE_CHECKING +import datetime # --- Third-party imports --- 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 # from image_processing import ... 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 receiver import UdpReceiver 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) 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 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): @@ -1145,6 +1147,42 @@ class App: logging.debug(f"{log_prefix} Skipping map update: MapIntegrationManager not available.") elif not geo_info or not geo_info.get("valid", False): 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 self._update_fps_stats("sar") # Updates self.state counters @@ -1794,6 +1832,8 @@ class App: ref_lat_rad = geo["lat"] # radians ref_lon_rad = geo["lon"] # radians orient_rad = geo.get("orientation", 0.0) # radians (default to 0 if missing) + + orient_rad = -orient_rad #inverse angle # --- Coordinate Transformation --- # 1. Normalize display coordinates to [0, 1] range diff --git a/config.py b/config.py index 43d8b6e..95ca6c3 100644 --- a/config.py +++ b/config.py @@ -171,5 +171,12 @@ SAR_IMAGE_SIZE_KM = ( 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 --- diff --git a/image_pipeline.py b/image_pipeline.py index b2140d3..401fe6f 100644 --- a/image_pipeline.py +++ b/image_pipeline.py @@ -124,6 +124,8 @@ class ImagePipeline: # --- Processing Steps --- 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 = -orient_rad # invert angle # 1. Apply B/C LUT (from state) logging.debug(f"{log_prefix} Applying B/C LUT...") diff --git a/utils.py b/utils.py index 0bcf162..c14e303 100644 --- a/utils.py +++ b/utils.py @@ -13,6 +13,28 @@ Uses standardized logging prefixes. Drop counts are now managed within AppState. import queue import logging 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) @@ -194,5 +216,188 @@ def decimal_to_dms(decimal_degrees, is_latitude): ) 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 ---