add map elevation, using module geoelevation.
This commit is contained in:
parent
30be7ea111
commit
dc581931a7
3
.gitignore
vendored
3
.gitignore
vendored
@ -8,4 +8,5 @@ _build/
|
||||
_dist/
|
||||
sar_images/
|
||||
kml_output/
|
||||
_req_packages/
|
||||
_req_packages/
|
||||
elevation_cache/
|
||||
@ -1,6 +1,3 @@
|
||||
# --- START OF FILE ControlPanel.py ---
|
||||
|
||||
# ControlPanel.py (Previously app.py)
|
||||
"""
|
||||
THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
|
||||
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
@ -22,7 +19,7 @@ import logging
|
||||
import math
|
||||
import sys
|
||||
import socket
|
||||
from typing import Optional, Tuple, Any, Dict, TYPE_CHECKING, Union, List
|
||||
from typing import Optional, Tuple, Any, Dict, TYPE_CHECKING, Union, List, Callable
|
||||
import datetime
|
||||
import tkinter.filedialog as fd
|
||||
from pathlib import Path
|
||||
@ -48,47 +45,32 @@ except ImportError:
|
||||
"[App Init] Pillow library not found. Map/Image functionality will fail."
|
||||
)
|
||||
|
||||
# --- Configuration Import ---
|
||||
from controlpanel import config
|
||||
|
||||
# --- Logging Setup ---
|
||||
# --- Absolute Imports for Application Modules ---
|
||||
try:
|
||||
from controlpanel import config # Config is now essential before GeoElevation logic
|
||||
from controlpanel.logging_config import setup_logging
|
||||
|
||||
setup_logging()
|
||||
except ImportError:
|
||||
print("ERROR: logging_config.py not found. Using basic logging.")
|
||||
logging.basicConfig(
|
||||
level=logging.WARNING,
|
||||
format="%(asctime)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s",
|
||||
from controlpanel.app_state import AppState
|
||||
# GUI related imports
|
||||
from controlpanel.gui.ui import ControlPanel as UIPanel, StatusBar, create_main_window
|
||||
from controlpanel.gui.display import DisplayManager
|
||||
# Core processing imports
|
||||
from controlpanel.core.receiver import UdpReceiver
|
||||
from controlpanel.core.test_mode_manager import TestModeManager
|
||||
from controlpanel.core.image_pipeline import ImagePipeline
|
||||
from controlpanel.core.image_recorder import ImageRecorder
|
||||
# Utility imports
|
||||
from controlpanel.utils.utils import (
|
||||
put_queue, clear_queue, decimal_to_dms, dms_string_to_decimal,
|
||||
generate_sar_kml, launch_google_earth, cleanup_kml_output_directory,
|
||||
open_google_maps, generate_lookat_and_point_kml, generate_composite_kml,
|
||||
_simplekml_available, _pyproj_available, format_ctypes_structure,
|
||||
)
|
||||
|
||||
# --- Application Modules Import ---
|
||||
from controlpanel.gui.ui import ControlPanel as UIPanel, StatusBar, create_main_window
|
||||
from controlpanel.gui.display import DisplayManager
|
||||
from controlpanel.utils.utils import (
|
||||
put_queue,
|
||||
clear_queue,
|
||||
decimal_to_dms,
|
||||
dms_string_to_decimal,
|
||||
generate_sar_kml, # Rimane invariato
|
||||
launch_google_earth,
|
||||
# cleanup_old_kml_files, # Rimuovere o commentare il vecchio import
|
||||
cleanup_kml_output_directory, # <<< NUOVO IMPORT
|
||||
open_google_maps,
|
||||
generate_lookat_and_point_kml,
|
||||
generate_composite_kml, # Modificato precedentemente per usare questa
|
||||
_simplekml_available,
|
||||
_pyproj_available,
|
||||
format_ctypes_structure,
|
||||
)
|
||||
from controlpanel.utils.network import create_udp_socket, close_udp_socket
|
||||
from controlpanel.core.receiver import UdpReceiver
|
||||
from controlpanel.app_state import AppState
|
||||
from controlpanel.core.test_mode_manager import TestModeManager
|
||||
from controlpanel.core.image_pipeline import ImagePipeline
|
||||
from controlpanel.utils.image_processing import load_image, normalize_image, apply_color_palette
|
||||
from controlpanel.core.image_recorder import ImageRecorder
|
||||
from controlpanel.utils.network import create_udp_socket, close_udp_socket
|
||||
from controlpanel.utils.image_processing import load_image, normalize_image, apply_color_palette
|
||||
except ImportError as app_import_err:
|
||||
print(f"FATAL ERROR: Failed to import core application modules: {app_import_err}")
|
||||
logging.critical(f"FATAL ERROR: Failed to import core application modules: {app_import_err}", exc_info=True)
|
||||
sys.exit(1)
|
||||
|
||||
# --- Map related imports (Conditional) ---
|
||||
map_libs_found = True
|
||||
@ -151,6 +133,97 @@ DEFAULT_COMMIT = "Unknown"
|
||||
DEFAULT_BRANCH = "Unknown"
|
||||
# --- End Constants ---
|
||||
|
||||
# --- >>> START OF NEW GEOELEVATION INTEGRATION CODE <<< ---
|
||||
# Global variables for GeoElevation availability and function reference
|
||||
GEOELEVATION_AVAILABLE: bool = False
|
||||
get_elevation_function: Optional[Callable] = None
|
||||
_geoelevation_log_prefix = "[GeoElevation Integration]"
|
||||
|
||||
try:
|
||||
# 1. Check if the path is configured in controlpanel/config.py
|
||||
configured_path_str = getattr(config, "GEOELEVATION_PROJECT_ROOT_PATH", None)
|
||||
|
||||
if configured_path_str and isinstance(configured_path_str, str) and configured_path_str.strip():
|
||||
configured_path = Path(configured_path_str.strip())
|
||||
logging.info(f"{_geoelevation_log_prefix} GEOELEVATION_PROJECT_ROOT_PATH configured to: '{configured_path_str}'")
|
||||
|
||||
# 2. Resolve to absolute path if relative
|
||||
# Relative path is assumed to be relative to the *ControlPanel project root*
|
||||
# (the directory containing the 'controlpanel' package folder).
|
||||
if not configured_path.is_absolute():
|
||||
# __file__ points to controlpanel/app_main.py
|
||||
# controlpanel_package_dir is controlpanel/
|
||||
controlpanel_package_dir = Path(__file__).parent
|
||||
# project_root_dir is the parent of controlpanel/
|
||||
project_root_dir = controlpanel_package_dir.parent
|
||||
absolute_geoelevation_path = (project_root_dir / configured_path).resolve()
|
||||
logging.debug(f"{_geoelevation_log_prefix} Resolved relative path '{configured_path}' to '{absolute_geoelevation_path}'")
|
||||
configured_path = absolute_geoelevation_path
|
||||
|
||||
# 3. Validate the path and the 'geoelevation' subfolder
|
||||
potential_module_dir = configured_path / "geoelevation" # The actual package folder
|
||||
if configured_path.is_dir() and potential_module_dir.is_dir() and (potential_module_dir / "__init__.py").is_file():
|
||||
logging.debug(f"{_geoelevation_log_prefix} Validated GeoElevation project root: '{configured_path}'")
|
||||
logging.debug(f"{_geoelevation_log_prefix} Potential GeoElevation module directory: '{potential_module_dir}'")
|
||||
|
||||
# 4. Add the *parent* of the 'geoelevation' module to sys.path
|
||||
# This allows `from geoelevation import ...`
|
||||
# So, we add `configured_path` to sys.path.
|
||||
path_to_add_to_sys = str(configured_path)
|
||||
path_added_to_sys = False
|
||||
if path_to_add_to_sys not in sys.path:
|
||||
sys.path.insert(0, path_to_add_to_sys) # Add to beginning for priority
|
||||
path_added_to_sys = True
|
||||
logging.info(f"{_geoelevation_log_prefix} Added '{path_to_add_to_sys}' to sys.path for GeoElevation import.")
|
||||
|
||||
# 5. Attempt to import the specific function
|
||||
try:
|
||||
from geoelevation import get_point_elevation as ge_get_point_elevation
|
||||
# from geoelevation import get_point_elevation as ge_get_point_elevation # If function is in __init__.py
|
||||
GEOELEVATION_AVAILABLE = True
|
||||
get_elevation_function = ge_get_point_elevation
|
||||
logging.info(f"{_geoelevation_log_prefix} Module 'geoelevation.elevation_service.get_point_elevation' loaded successfully. Elevation feature ENABLED.")
|
||||
|
||||
# Optional: Clean up sys.path if it was modified for this import only
|
||||
# This is generally good practice to avoid polluting sys.path permanently
|
||||
if path_added_to_sys and path_to_add_to_sys in sys.path:
|
||||
try:
|
||||
sys.path.remove(path_to_add_to_sys)
|
||||
logging.debug(f"{_geoelevation_log_prefix} Removed '{path_to_add_to_sys}' from sys.path after successful import.")
|
||||
except ValueError:
|
||||
pass # Should not happen if path_added_to_sys is True
|
||||
|
||||
except ImportError as e_import:
|
||||
logging.warning(f"{_geoelevation_log_prefix} Failed to import 'get_point_elevation' from GeoElevation module at '{potential_module_dir}': {e_import}. Elevation feature DISABLED.")
|
||||
GEOELEVATION_AVAILABLE = False
|
||||
# If import failed, and we added the path, consider removing it too
|
||||
if path_added_to_sys and path_to_add_to_sys in sys.path:
|
||||
try:
|
||||
sys.path.remove(path_to_add_to_sys)
|
||||
logging.debug(f"{_geoelevation_log_prefix} Removed '{path_to_add_to_sys}' from sys.path after import failure.")
|
||||
except ValueError:
|
||||
pass
|
||||
except Exception as e_general_import:
|
||||
logging.error(f"{_geoelevation_log_prefix} An unexpected error occurred during GeoElevation import: {e_general_import}. Elevation feature DISABLED.", exc_info=True)
|
||||
GEOELEVATION_AVAILABLE = False
|
||||
if path_added_to_sys and path_to_add_to_sys in sys.path: # Cleanup
|
||||
try: sys.path.remove(path_to_add_to_sys)
|
||||
except ValueError: pass
|
||||
|
||||
|
||||
else: # Path not valid (doesn't exist or geoelevation subfolder missing)
|
||||
logging.warning(f"{_geoelevation_log_prefix} Configured GeoElevation path '{configured_path}' or its 'geoelevation' subfolder (with __init__.py) is not valid. Elevation feature DISABLED.")
|
||||
GEOELEVATION_AVAILABLE = False
|
||||
else: # Path not configured or empty
|
||||
logging.info(f"{_geoelevation_log_prefix} GEOELEVATION_PROJECT_ROOT_PATH not configured or empty. Elevation feature DISABLED.")
|
||||
GEOELEVATION_AVAILABLE = False
|
||||
|
||||
except Exception as e_outer_config:
|
||||
# Catch any error during the config reading/path manipulation itself
|
||||
logging.error(f"{_geoelevation_log_prefix} Error processing GeoElevation configuration: {e_outer_config}. Elevation feature DISABLED.", exc_info=True)
|
||||
GEOELEVATION_AVAILABLE = False
|
||||
# --- >>> END OF NEW GEOELEVATION INTEGRATION CODE <<< ---
|
||||
|
||||
|
||||
# --- Main Application Class ---
|
||||
class ControlPanelApp:
|
||||
@ -362,8 +435,16 @@ class ControlPanelApp:
|
||||
|
||||
# Set initial mode (Test or Normal/Local)
|
||||
self.update_image_mode()
|
||||
|
||||
# --- Add a new attribute for elevation request tracking ---
|
||||
self.active_elevation_requests: Dict[str, threading.Thread] = {} # Key: "src_lat_lon", Value: Thread
|
||||
|
||||
|
||||
logging.info(f"{log_prefix} Application initialization complete.")
|
||||
if GEOELEVATION_AVAILABLE:
|
||||
logging.info(f"{log_prefix} GeoElevation functionality is AVAILABLE.")
|
||||
else:
|
||||
logging.warning(f"{log_prefix} GeoElevation functionality is UNAVAILABLE.")
|
||||
|
||||
# --- Status Update Method ---
|
||||
def set_status(self, message: str):
|
||||
@ -405,6 +486,148 @@ class ControlPanelApp:
|
||||
self.root.after_idle(_update)
|
||||
except Exception as e:
|
||||
logging.warning(f"{log_prefix} Error scheduling status update: {e}")
|
||||
|
||||
def request_elevation_for_point(self, source_description: str, latitude: float, longitude: float):
|
||||
"""
|
||||
Requests elevation for a given point asynchronously if GeoElevation is available.
|
||||
A unique identifier for the request is created to manage concurrent requests.
|
||||
|
||||
Args:
|
||||
source_description (str): A description of the point source (e.g., "SAR_Center", "SAR_Mouse_Click").
|
||||
latitude (float): Latitude of the point in decimal degrees.
|
||||
longitude (float): Longitude of the point in decimal degrees.
|
||||
"""
|
||||
_log_prefix_req = "[App Elev Req]"
|
||||
if not GEOELEVATION_AVAILABLE or get_elevation_function is None:
|
||||
logging.warning(f"{_log_prefix_req} GeoElevation not available. Cannot request elevation for {source_description}.")
|
||||
# Optionally, update UI to indicate unavailability if this is the first time
|
||||
self._update_elevation_display(source_description, "GeoElevation N/A")
|
||||
return
|
||||
|
||||
if not (math.isfinite(latitude) and math.isfinite(longitude)):
|
||||
logging.warning(f"{_log_prefix_req} Invalid coordinates for {source_description}: Lat={latitude}, Lon={longitude}. Skipping elevation request.")
|
||||
self._update_elevation_display(source_description, "Invalid Coords")
|
||||
return
|
||||
|
||||
# Create a unique request ID based on source and coordinates to prevent duplicate threads for the exact same point if clicked rapidly
|
||||
# This is a simple way; more robust would involve a proper task queue with cancellation.
|
||||
request_id = f"{source_description}_{latitude:.6f}_{longitude:.6f}"
|
||||
|
||||
# Check if a request for this exact ID is already running
|
||||
if request_id in self.active_elevation_requests and self.active_elevation_requests[request_id].is_alive():
|
||||
logging.debug(f"{_log_prefix_req} Elevation request for '{request_id}' is already active. Skipping.")
|
||||
return
|
||||
|
||||
logging.info(f"{_log_prefix_req} Requesting elevation for {source_description} at ({latitude:.4f}, {longitude:.4f}). Request ID: {request_id}")
|
||||
self._update_elevation_display(source_description, "Elevation: Requesting...")
|
||||
|
||||
# Create and start a new thread for the elevation call
|
||||
elevation_thread = threading.Thread(
|
||||
target=self._get_elevation_worker,
|
||||
args=(request_id, source_description, latitude, longitude, get_elevation_function),
|
||||
name=f"ElevationWorker-{request_id}",
|
||||
daemon=True
|
||||
)
|
||||
self.active_elevation_requests[request_id] = elevation_thread
|
||||
elevation_thread.start()
|
||||
|
||||
def _get_elevation_worker(
|
||||
self,
|
||||
request_id: str,
|
||||
source_description: str,
|
||||
latitude: float,
|
||||
longitude: float,
|
||||
elevation_func: Callable
|
||||
):
|
||||
"""
|
||||
Worker function executed in a separate thread to call the GeoElevation service.
|
||||
Sends the result or error back to the main thread via the Tkinter queue.
|
||||
"""
|
||||
_log_prefix_worker = f"[App Elev Work] ({request_id})"
|
||||
# --- MODIFIED PAYLOAD STRUCTURE ---
|
||||
result_payload: Dict[str, Any] = {
|
||||
"request_id": request_id,
|
||||
"source_description": source_description,
|
||||
"latitude": latitude,
|
||||
"longitude": longitude,
|
||||
"elevation_value": None, # Stores the numeric elevation or None/NaN
|
||||
"display_text": "Elev: Error", # Default display text in case of issues
|
||||
"error": None
|
||||
}
|
||||
|
||||
try:
|
||||
logging.debug(f"{_log_prefix_worker} Calling GeoElevation function...")
|
||||
elevation = elevation_func(latitude, longitude, show_progress_dialog=False)
|
||||
logging.debug(f"{_log_prefix_worker} GeoElevation call returned: {elevation}")
|
||||
|
||||
if elevation is None:
|
||||
result_payload["display_text"] = "Elev: N/A" # More concise
|
||||
result_payload["elevation_value"] = None
|
||||
elif elevation != elevation: # Check for NaN
|
||||
result_payload["display_text"] = "Elev: NoData" # More concise
|
||||
result_payload["elevation_value"] = float('nan') # Store NaN
|
||||
else:
|
||||
numeric_elevation = float(elevation)
|
||||
result_payload["elevation_value"] = numeric_elevation
|
||||
result_payload["display_text"] = f"{numeric_elevation:.1f} m" # Desired format
|
||||
except RuntimeError as e_rt:
|
||||
logging.error(f"{_log_prefix_worker} GeoElevation RuntimeError: {e_rt}")
|
||||
result_payload["display_text"] = "Elev: Err(RT)"
|
||||
result_payload["error"] = str(e_rt)
|
||||
except Exception as e_generic:
|
||||
logging.exception(f"{_log_prefix_worker} Unexpected error calling GeoElevation:")
|
||||
result_payload["display_text"] = "Elev: Err(Sys)"
|
||||
result_payload["error"] = str(e_generic)
|
||||
finally:
|
||||
if hasattr(self, 'tkinter_queue') and self.tkinter_queue is not None:
|
||||
put_queue(self.tkinter_queue, ("ELEVATION_RESULT", result_payload), "tkinter", self)
|
||||
if request_id in self.active_elevation_requests:
|
||||
del self.active_elevation_requests[request_id]
|
||||
logging.debug(f"{_log_prefix_worker} Worker finished.")
|
||||
|
||||
|
||||
def _update_elevation_display(self, source_description: str, display_text_value: str):
|
||||
"""
|
||||
Updates the UI to show the elevation or status by calling the
|
||||
appropriate method on the control_panel (UIPanel) instance.
|
||||
|
||||
Args:
|
||||
source_description (str): Describes the source of the elevation point.
|
||||
Should match one of config.ELEVATION_SOURCE_* constants.
|
||||
display_text_value (str): The pre-formatted text to display (e.g., "200.0 m", "Elev: N/A").
|
||||
"""
|
||||
_log_prefix_disp = "[App Elev Disp]"
|
||||
logging.debug(f"{_log_prefix_disp} For '{source_description}': {display_text_value}")
|
||||
|
||||
control_panel_ui = getattr(self, "control_panel", None)
|
||||
if not control_panel_ui:
|
||||
logging.error(f"{_log_prefix_disp} ControlPanel UI instance not found. Cannot update elevation display.")
|
||||
return
|
||||
|
||||
try:
|
||||
if source_description == config.ELEVATION_SOURCE_SAR_CENTER:
|
||||
if hasattr(control_panel_ui, 'set_sar_center_elevation'):
|
||||
control_panel_ui.set_sar_center_elevation(display_text_value)
|
||||
else:
|
||||
logging.warning(f"{_log_prefix_disp} Method 'set_sar_center_elevation' not found.")
|
||||
elif source_description == config.ELEVATION_SOURCE_SAR_MOUSE:
|
||||
if hasattr(control_panel_ui, 'set_sar_mouse_elevation'):
|
||||
control_panel_ui.set_sar_mouse_elevation(display_text_value)
|
||||
else:
|
||||
logging.warning(f"{_log_prefix_disp} Method 'set_sar_mouse_elevation' not found.")
|
||||
elif source_description == config.ELEVATION_SOURCE_MAP_MOUSE:
|
||||
if hasattr(control_panel_ui, 'set_map_mouse_elevation'):
|
||||
control_panel_ui.set_map_mouse_elevation(display_text_value)
|
||||
else:
|
||||
logging.warning(f"{_log_prefix_disp} Method 'set_map_mouse_elevation' not found.")
|
||||
else:
|
||||
logging.warning(f"{_log_prefix_disp} Unknown elevation source '{source_description}'. No specific UI label updated.")
|
||||
# You might still want to update a general status if needed:
|
||||
# self.statusbar.set_status_text(f"Elevation Info: [{source_description}] {display_text_value}")
|
||||
|
||||
except Exception as e:
|
||||
logging.exception(f"{_log_prefix_disp} Error updating elevation display for '{source_description}':")
|
||||
|
||||
|
||||
# --- LUT Generation Methods ---
|
||||
def update_brightness_contrast_lut(self):
|
||||
@ -1811,27 +2034,26 @@ class ControlPanelApp:
|
||||
|
||||
# --- UI Display Update Helpers ---
|
||||
def _update_sar_ui_labels(self):
|
||||
"""Updates SAR related UI Entry widgets from AppState."""
|
||||
"""Updates SAR related UI Entry widgets from AppState and requests SAR center elevation."""
|
||||
cp = getattr(self, "control_panel", None)
|
||||
# Check if control panel exists and its window is valid
|
||||
if not cp or not cp.winfo_exists():
|
||||
return
|
||||
|
||||
# ... (codice esistente per aggiornare lat_s, lon_s, orient_s, size_s) ...
|
||||
geo = self.state.current_sar_geo_info
|
||||
lat_s, lon_s, orient_s, size_s = "N/A", "N/A", "N/A", "N/A"
|
||||
is_valid = geo and geo.get("valid")
|
||||
is_valid_geo_for_ui = geo and geo.get("valid")
|
||||
lat_deg_for_elevation: Optional[float] = None
|
||||
lon_deg_for_elevation: Optional[float] = None
|
||||
|
||||
if is_valid:
|
||||
if is_valid_geo_for_ui:
|
||||
try:
|
||||
# Convert radians to degrees for display/formatting
|
||||
lat_d = math.degrees(geo["lat"])
|
||||
lon_d = math.degrees(geo["lon"])
|
||||
lat_deg_for_elevation = lat_d # Store for elevation request
|
||||
lon_deg_for_elevation = lon_d # Store for elevation request
|
||||
# ... (resto della formattazione per lat_s, lon_s, etc.) ...
|
||||
orient_d = math.degrees(geo.get("orientation", 0.0))
|
||||
# Format using DMS utility
|
||||
lat_s = decimal_to_dms(lat_d, True)
|
||||
lon_s = decimal_to_dms(lon_d, False)
|
||||
orient_s = f"{orient_d:.2f}°" # Format orientation
|
||||
# Calculate size in km if possible
|
||||
orient_s = f"{orient_d:.2f}°"
|
||||
scale_x = geo.get("scale_x", 0.0)
|
||||
width_px = geo.get("width_px", 0)
|
||||
scale_y = geo.get("scale_y", 0.0)
|
||||
@ -1840,25 +2062,46 @@ class ControlPanelApp:
|
||||
size_w_km = (scale_x * width_px) / 1000.0
|
||||
size_h_km = (scale_y * height_px) / 1000.0
|
||||
size_s = f"W: {size_w_km:.1f} km, H: {size_h_km:.1f} km"
|
||||
|
||||
except Exception as e:
|
||||
logging.warning(f"[App UI Update] Error formatting SAR geo labels: {e}")
|
||||
lat_s, lon_s, orient_s, size_s = "Error", "Error", "Error", "Error"
|
||||
is_valid = False # Mark as invalid if formatting fails
|
||||
is_valid_geo_for_ui = False
|
||||
lat_deg_for_elevation = None # Invalidate
|
||||
lon_deg_for_elevation = None # Invalidate
|
||||
|
||||
# Update UI widgets safely using control panel methods
|
||||
# Update UI widgets
|
||||
# ... (chiamate a cp.set_sar_center_coords, etc.) ...
|
||||
try:
|
||||
cp.set_sar_center_coords(lat_s, lon_s)
|
||||
cp.set_sar_orientation(orient_s)
|
||||
cp.set_sar_size_km(size_s)
|
||||
if cp and cp.winfo_exists():
|
||||
cp.set_sar_center_coords(lat_s, lon_s)
|
||||
cp.set_sar_orientation(orient_s)
|
||||
cp.set_sar_size_km(size_s)
|
||||
except Exception as e:
|
||||
# Catch errors if UI elements don't exist or Tcl errors
|
||||
logging.exception(f"[App UI Update] Error setting SAR labels: {e}")
|
||||
|
||||
|
||||
# --- >>> START OF NEW ELEVATION REQUEST FOR SAR CENTER <<< ---
|
||||
if is_valid_geo_for_ui and lat_deg_for_elevation is not None and lon_deg_for_elevation is not None:
|
||||
self.request_elevation_for_point(
|
||||
source_description=config.ELEVATION_SOURCE_SAR_CENTER, # Define this in config.py
|
||||
latitude=lat_deg_for_elevation,
|
||||
longitude=lon_deg_for_elevation
|
||||
)
|
||||
else:
|
||||
# Clear or indicate N/A for SAR center elevation if geo is invalid
|
||||
self._update_elevation_display(config.ELEVATION_SOURCE_SAR_CENTER, "GeoInfo N/A")
|
||||
# --- >>> END OF NEW ELEVATION REQUEST FOR SAR CENTER <<< ---
|
||||
|
||||
# Clear mouse coords if GeoInfo becomes invalid
|
||||
if not is_valid:
|
||||
if not is_valid_geo_for_ui:
|
||||
try:
|
||||
cp.set_mouse_coordinates("N/A", "N/A")
|
||||
cp.set_map_mouse_coordinates("N/A", "N/A")
|
||||
if cp and cp.winfo_exists():
|
||||
cp.set_mouse_coordinates("N/A", "N/A")
|
||||
cp.set_map_mouse_coordinates("N/A", "N/A")
|
||||
# Clear mouse elevation displays too
|
||||
self._update_elevation_display(config.ELEVATION_SOURCE_SAR_MOUSE, "GeoInfo N/A")
|
||||
self._update_elevation_display(config.ELEVATION_SOURCE_MAP_MOUSE, "GeoInfo N/A")
|
||||
except Exception:
|
||||
pass # Ignore errors if UI closed
|
||||
|
||||
@ -2043,6 +2286,8 @@ class ControlPanelApp:
|
||||
self._handle_map_click_update(payload)
|
||||
elif command == "SAR_METADATA_UPDATE":
|
||||
self._handle_sar_metadata_update(payload)
|
||||
elif command == "ELEVATION_RESULT":
|
||||
self._handle_elevation_result(payload)
|
||||
else:
|
||||
# Log if an unknown command is received
|
||||
logging.warning(f"{log_prefix} Unknown command: {command}")
|
||||
@ -2110,6 +2355,32 @@ class ControlPanelApp:
|
||||
cp.set_map_mouse_coordinates("N/A", "N/A")
|
||||
except Exception:
|
||||
pass # Ignore errors if UI closed
|
||||
|
||||
def _handle_elevation_result(self, payload: Dict[str, Any]):
|
||||
"""
|
||||
Handles the ELEVATION_RESULT command from the Tkinter queue.
|
||||
Updates the UI with the elevation information.
|
||||
"""
|
||||
_log_prefix_res = "[App Elev Res]"
|
||||
if self.state.shutting_down:
|
||||
return
|
||||
|
||||
try:
|
||||
source_desc = payload.get("source_description", "UnknownPoint")
|
||||
# --- USE THE PRE-FORMATTED DISPLAY_TEXT FROM PAYLOAD ---
|
||||
display_text_for_ui = payload.get("display_text", "Elev: Error")
|
||||
error_details = payload.get("error")
|
||||
# elevation_val = payload.get("elevation_value") # Numeric value, can be used for other logic if needed
|
||||
|
||||
logging.info(f"{_log_prefix_res} Received elevation result for '{source_desc}': {display_text_for_ui}")
|
||||
if error_details:
|
||||
logging.error(f"{_log_prefix_res} Error details for '{source_desc}': {error_details}")
|
||||
|
||||
self._update_elevation_display(source_desc, display_text_for_ui)
|
||||
|
||||
except Exception as e:
|
||||
logging.exception(f"{_log_prefix_res} Error handling elevation result payload: {payload}")
|
||||
self._update_elevation_display("Error", "Elev: UI Error")
|
||||
|
||||
def _handle_show_map_update(self, payload: Optional[ImageType]):
|
||||
"""Handles the SHOW_MAP command by delegating display to MapIntegrationManager."""
|
||||
@ -2144,36 +2415,101 @@ class ControlPanelApp:
|
||||
)
|
||||
|
||||
def _handle_sar_click_update(self, payload: Optional[Tuple[int, int]]):
|
||||
"""Updates the SAR click coordinates state and triggers a SAR redraw."""
|
||||
"""Updates the SAR click coordinates state, triggers a SAR redraw, and requests elevation."""
|
||||
log_prefix = "[App SAR Click State]"
|
||||
if self.state.shutting_down:
|
||||
return
|
||||
# Validate payload (should be (x, y) tuple)
|
||||
# ... (codice esistente per aggiornare self.state.last_sar_click_coords e self._trigger_sar_update()) ...
|
||||
if payload and isinstance(payload, tuple) and len(payload) == 2:
|
||||
# Store the pixel coordinates in AppState
|
||||
self.state.last_sar_click_coords = payload
|
||||
logging.debug(
|
||||
f"{log_prefix} Updated state.last_sar_click_coords to {payload}"
|
||||
)
|
||||
# Trigger SAR redraw pipeline to include the marker
|
||||
self._trigger_sar_update()
|
||||
logging.debug(f"{log_prefix} Updated state.last_sar_click_coords to {payload}")
|
||||
self._trigger_sar_update() # Redraw SAR image with marker
|
||||
|
||||
# --- >>> START OF NEW ELEVATION REQUEST <<< ---
|
||||
# Get geographic coordinates for the clicked SAR pixel
|
||||
# This reuses the logic that updates the "SAR Mouse" display
|
||||
# It's a bit indirect but avoids duplicating the geo calculation here.
|
||||
# Assumes process_mouse_queue will eventually update the UI stringvar
|
||||
# and then we parse it back. A more direct way would be to have
|
||||
# get_geo_coords_from_sar_pixel return the lat/lon directly.
|
||||
|
||||
# For now, let's assume we need to convert payload (pixel) to geo
|
||||
geo_info = self.state.current_sar_geo_info
|
||||
disp_w = self.state.sar_display_width
|
||||
disp_h = self.state.sar_display_height
|
||||
|
||||
if (geo_info and geo_info.get("valid") and disp_w > 0 and disp_h > 0 and
|
||||
geo_info.get("width_px", 0) > 0 and geo_info.get("height_px", 0) > 0):
|
||||
try:
|
||||
x_disp, y_disp = payload
|
||||
orig_w, orig_h = geo_info["width_px"], geo_info["height_px"]
|
||||
scale_x, scale_y = geo_info["scale_x"], geo_info["scale_y"]
|
||||
ref_x, ref_y = geo_info["ref_x"], geo_info["ref_y"]
|
||||
ref_lat_rad, ref_lon_rad = geo_info["lat"], geo_info["lon"]
|
||||
# angle_rad = geo_info.get("orientation", 0.0) # Simplified calc doesn't use angle here
|
||||
|
||||
orig_x_f = (x_disp / disp_w) * orig_w
|
||||
orig_y_f = (y_disp / disp_h) * orig_h
|
||||
pixel_delta_x_f = orig_x_f - ref_x
|
||||
pixel_delta_y_f = ref_y - orig_y_f
|
||||
meters_delta_x = pixel_delta_x_f * scale_x
|
||||
meters_delta_y = pixel_delta_y_f * scale_y
|
||||
|
||||
M_PER_DLAT = 111132.954
|
||||
M_PER_DLON_EQ = 111319.488
|
||||
m_per_dlon_val = max(abs(M_PER_DLON_EQ * math.cos(ref_lat_rad)), 1e-3)
|
||||
lat_offset_deg_val = meters_delta_y / M_PER_DLAT
|
||||
lon_offset_deg_val = meters_delta_x / m_per_dlon_val
|
||||
final_lat_deg_val = math.degrees(ref_lat_rad) + lat_offset_deg_val
|
||||
final_lon_deg_val = math.degrees(ref_lon_rad) + lon_offset_deg_val
|
||||
|
||||
if math.isfinite(final_lat_deg_val) and abs(final_lat_deg_val) <= 90.0 and \
|
||||
math.isfinite(final_lon_deg_val) and abs(final_lon_deg_val) <= 180.0:
|
||||
self.request_elevation_for_point(
|
||||
source_description=config.ELEVATION_SOURCE_SAR_MOUSE, # Define this in config.py
|
||||
latitude=final_lat_deg_val,
|
||||
longitude=final_lon_deg_val
|
||||
)
|
||||
else:
|
||||
logging.warning(f"{log_prefix} Calculated invalid geo-coords for SAR click, skipping elevation.")
|
||||
self._update_elevation_display(config.ELEVATION_SOURCE_SAR_MOUSE, "Invalid Geo")
|
||||
|
||||
except Exception as e_geo_calc:
|
||||
logging.exception(f"{log_prefix} Error calculating geo-coords for SAR click elevation request: {e_geo_calc}")
|
||||
self._update_elevation_display(config.ELEVATION_SOURCE_SAR_MOUSE, "Geo Calc Err")
|
||||
else:
|
||||
logging.warning(f"{log_prefix} Insufficient data for SAR click geo-calculation, skipping elevation.")
|
||||
self._update_elevation_display(config.ELEVATION_SOURCE_SAR_MOUSE, "Geo Data N/A")
|
||||
# --- >>> END OF NEW ELEVATION REQUEST <<< ---
|
||||
else:
|
||||
logging.warning(f"{log_prefix} Received invalid payload: {payload}")
|
||||
|
||||
def _handle_map_click_update(self, payload: Optional[Tuple[int, int]]):
|
||||
"""Updates the Map click coordinates state and triggers a Map redraw."""
|
||||
"""Updates the Map click coordinates state, triggers a Map redraw, and requests elevation."""
|
||||
log_prefix = "[App Map Click State]"
|
||||
if self.state.shutting_down:
|
||||
return
|
||||
# Validate payload
|
||||
# ... (codice esistente per aggiornare self.state.last_map_click_coords e self.trigger_map_redraw()) ...
|
||||
if payload and isinstance(payload, tuple) and len(payload) == 2:
|
||||
# Store the pixel coordinates in AppState
|
||||
self.state.last_map_click_coords = payload
|
||||
logging.debug(
|
||||
f"{log_prefix} Updated state.last_map_click_coords to {payload}"
|
||||
)
|
||||
# Trigger map redraw (recomposition should pick up the new marker state)
|
||||
self.trigger_map_redraw(full_update=False)
|
||||
logging.debug(f"{log_prefix} Updated state.last_map_click_coords to {payload}")
|
||||
self.trigger_map_redraw(full_update=False) # Redraw map with marker
|
||||
|
||||
# --- >>> START OF NEW ELEVATION REQUEST <<< ---
|
||||
mgr = getattr(self, "map_integration_manager", None)
|
||||
if mgr:
|
||||
pixel_x, pixel_y = payload
|
||||
geo_coords = mgr.get_geo_coords_from_map_pixel(pixel_x, pixel_y)
|
||||
if geo_coords:
|
||||
lat_deg, lon_deg = geo_coords
|
||||
self.request_elevation_for_point(
|
||||
source_description=config.ELEVATION_SOURCE_MAP_MOUSE, # Define this in config.py
|
||||
latitude=lat_deg,
|
||||
longitude=lon_deg
|
||||
)
|
||||
else:
|
||||
logging.warning(f"{log_prefix} Could not get geo-coords for map click, skipping elevation.")
|
||||
self._update_elevation_display(config.ELEVATION_SOURCE_MAP_MOUSE, "Geo Conv Err")
|
||||
else:
|
||||
logging.warning(f"{log_prefix} MapIntegrationManager not available for map click elevation request.")
|
||||
self._update_elevation_display(config.ELEVATION_SOURCE_MAP_MOUSE, "Map Mgr N/A")
|
||||
# --- >>> END OF NEW ELEVATION REQUEST <<< ---
|
||||
else:
|
||||
logging.warning(f"{log_prefix} Received invalid payload: {payload}")
|
||||
|
||||
|
||||
@ -205,4 +205,18 @@ KML_OUTPUT_DIRECTORY = "kml_output"
|
||||
AUTO_LAUNCH_GOOGLE_EARTH = False
|
||||
MAX_KML_FILES = 30 # Max KMLs to keep (0 or less disables cleanup)
|
||||
|
||||
|
||||
# Path to the root directory of the GeoElevation project
|
||||
# (i.e. the folder that CONTAINS the subfolder 'geoelevation')
|
||||
# Leave blank or None if GeoElevation should not be used.
|
||||
# Examples:
|
||||
# GEOELEVATION_PROJECT_ROOT_PATH = "/Users/yourname/development/GeoElevation" (absolute)
|
||||
# GEOELEVATION_PROJECT_ROOT_PATH = "../GeoElevation" (relative to the MapViewer root)
|
||||
GEOELEVATION_PROJECT_ROOT_PATH = r"C:\src\____GitProjects\GeoElevation" # Or the correct path
|
||||
# String constants for identifying the source of an elevation request.
|
||||
# These are used internally in app_main.py to manage UI updates.
|
||||
ELEVATION_SOURCE_SAR_CENTER = "SAR_Center"
|
||||
ELEVATION_SOURCE_SAR_MOUSE = "SAR_Mouse"
|
||||
ELEVATION_SOURCE_MAP_MOUSE = "Map_Mouse"
|
||||
|
||||
# --- END OF FILE config.py ---
|
||||
|
||||
@ -62,15 +62,17 @@ class ControlPanel(ttk.Frame): # This is the main panel holding user controls
|
||||
)
|
||||
self.dropped_stats_var = tk.StringVar(value="Drop (Q): S=0, M=0, Tk=0, Mo=0")
|
||||
self.incomplete_stats_var = tk.StringVar(value="Incmpl (RX): S=0, M=0")
|
||||
# Checkbox variable for metadata toggle (still needed here)
|
||||
self.show_meta_var = tk.BooleanVar(value=self.app.state.display_sar_metadata)
|
||||
|
||||
self.sar_center_elevation_var = tk.StringVar(value="Elev: N/A")
|
||||
self.sar_mouse_elevation_var = tk.StringVar(value="Elev: N/A")
|
||||
self.map_mouse_elevation_var = tk.StringVar(value="Elev: N/A")
|
||||
|
||||
# --- References to UI widgets ---
|
||||
self.mfd_color_labels: Dict[str, tk.Label] = {}
|
||||
# References to metadata widgets are REMOVED from this class
|
||||
|
||||
# --- Initialize UI structure ---
|
||||
self.init_ui() # Call the UI building method
|
||||
self.init_ui()
|
||||
|
||||
logging.debug(f"{log_prefix} ControlPanel frame initialization complete.")
|
||||
|
||||
@ -79,510 +81,311 @@ class ControlPanel(ttk.Frame): # This is the main panel holding user controls
|
||||
"""Initializes and arranges the user interface widgets within this frame."""
|
||||
log_prefix = "[UI Setup]"
|
||||
logging.debug(f"{log_prefix} Starting init_ui widget creation...")
|
||||
# This frame (self) is placed by its parent (container_frame in App)
|
||||
# DO NOT pack or grid self here.
|
||||
|
||||
# --- 1. SAR Parameters Frame ---
|
||||
# ... (Questa sezione rimane invariata) ...
|
||||
self.sar_params_frame = ttk.Labelframe(self, text="SAR Parameters", padding=5)
|
||||
self.sar_params_frame.pack(side=tk.TOP, fill=tk.X, padx=5, pady=(5, 2))
|
||||
|
||||
sar_row = 0 # Row counter for SAR grid
|
||||
|
||||
# Test Image Checkbox
|
||||
sar_row = 0
|
||||
self.test_image_var = tk.IntVar(value=1 if config.ENABLE_TEST_MODE else 0)
|
||||
self.test_image_check = ttk.Checkbutton(
|
||||
self.sar_params_frame,
|
||||
text="Test Image",
|
||||
variable=self.test_image_var,
|
||||
self.sar_params_frame, text="Test Image", variable=self.test_image_var,
|
||||
command=self.app.update_image_mode,
|
||||
)
|
||||
self.test_image_check.grid(
|
||||
row=sar_row, column=0, columnspan=2, sticky=tk.W, padx=5, pady=2
|
||||
)
|
||||
|
||||
# Record SAR Checkbox
|
||||
self.test_image_check.grid(row=sar_row, column=0, columnspan=2, sticky=tk.W, padx=5, pady=2)
|
||||
self.record_sar_var = tk.BooleanVar(value=config.DEFAULT_SAR_RECORDING_ENABLED)
|
||||
self.record_sar_check = ttk.Checkbutton(
|
||||
self.sar_params_frame,
|
||||
text="Record SAR",
|
||||
variable=self.record_sar_var,
|
||||
self.sar_params_frame, text="Record SAR", variable=self.record_sar_var,
|
||||
command=self.app.toggle_sar_recording,
|
||||
)
|
||||
self.record_sar_check.grid(
|
||||
row=sar_row, column=2, columnspan=2, sticky=tk.W, padx=5, pady=2
|
||||
)
|
||||
|
||||
self.record_sar_check.grid(row=sar_row, column=2, columnspan=2, sticky=tk.W, padx=5, pady=2)
|
||||
sar_row += 1
|
||||
|
||||
# SAR Size Combobox
|
||||
self.sar_size_label = ttk.Label(self.sar_params_frame, text="Size:")
|
||||
self.sar_size_label.grid(
|
||||
row=sar_row, column=0, sticky=tk.W, padx=(5, 2), pady=1
|
||||
)
|
||||
self.sar_size_label.grid(row=sar_row, column=0, sticky=tk.W, padx=(5, 2), pady=1)
|
||||
self.sar_size_combo = ttk.Combobox(
|
||||
self.sar_params_frame,
|
||||
values=config.SAR_SIZE_FACTORS,
|
||||
state="readonly",
|
||||
width=6,
|
||||
self.sar_params_frame, values=config.SAR_SIZE_FACTORS, state="readonly", width=6,
|
||||
)
|
||||
try: # Set initial value based on current state
|
||||
try:
|
||||
factor = 1
|
||||
if self.app.state.sar_display_width > 0:
|
||||
factor = max(1, config.SAR_WIDTH // self.app.state.sar_display_width)
|
||||
sz_str = f"1:{factor}"
|
||||
if sz_str in config.SAR_SIZE_FACTORS:
|
||||
self.sar_size_combo.set(sz_str)
|
||||
else:
|
||||
self.sar_size_combo.set(config.DEFAULT_SAR_SIZE)
|
||||
except Exception: # Fallback to default if error
|
||||
self.sar_size_combo.set(config.DEFAULT_SAR_SIZE)
|
||||
self.sar_size_combo.grid(
|
||||
row=sar_row, column=1, sticky=tk.EW, padx=(0, 10), pady=1
|
||||
)
|
||||
if sz_str in config.SAR_SIZE_FACTORS: self.sar_size_combo.set(sz_str)
|
||||
else: self.sar_size_combo.set(config.DEFAULT_SAR_SIZE)
|
||||
except Exception: self.sar_size_combo.set(config.DEFAULT_SAR_SIZE)
|
||||
self.sar_size_combo.grid(row=sar_row, column=1, sticky=tk.EW, padx=(0, 10), pady=1)
|
||||
self.sar_size_combo.bind("<<ComboboxSelected>>", self.app.update_sar_size)
|
||||
|
||||
# SAR Palette Combobox
|
||||
self.palette_label = ttk.Label(self.sar_params_frame, text="Palette:")
|
||||
self.palette_label.grid(row=sar_row, column=2, sticky=tk.W, padx=(0, 2), pady=1)
|
||||
self.palette_combo = ttk.Combobox(
|
||||
self.sar_params_frame,
|
||||
values=config.COLOR_PALETTES,
|
||||
state="readonly",
|
||||
width=8,
|
||||
)
|
||||
self.palette_combo.set(self.app.state.sar_palette) # Set from state
|
||||
self.palette_combo.grid(
|
||||
row=sar_row, column=3, sticky=tk.EW, padx=(0, 5), pady=1
|
||||
self.sar_params_frame, values=config.COLOR_PALETTES, state="readonly", width=8,
|
||||
)
|
||||
self.palette_combo.set(self.app.state.sar_palette)
|
||||
self.palette_combo.grid(row=sar_row, column=3, sticky=tk.EW, padx=(0, 5), pady=1)
|
||||
self.palette_combo.bind("<<ComboboxSelected>>", self.app.update_sar_palette)
|
||||
|
||||
sar_row += 1
|
||||
|
||||
# SAR Contrast Slider
|
||||
self.contrast_label = ttk.Label(self.sar_params_frame, text="Contrast:")
|
||||
self.contrast_label.grid(
|
||||
row=sar_row, column=0, sticky=tk.W, padx=(5, 2), pady=1
|
||||
)
|
||||
self.contrast_label.grid(row=sar_row, column=0, sticky=tk.W, padx=(5, 2), pady=1)
|
||||
self.contrast_scale = ttk.Scale(
|
||||
self.sar_params_frame,
|
||||
orient=tk.HORIZONTAL,
|
||||
from_=0.1,
|
||||
to=3.0,
|
||||
value=self.app.state.sar_contrast, # Set from state
|
||||
command=self.app.update_contrast,
|
||||
self.sar_params_frame, orient=tk.HORIZONTAL, from_=0.1, to=3.0,
|
||||
value=self.app.state.sar_contrast, command=self.app.update_contrast,
|
||||
)
|
||||
self.contrast_scale.grid(
|
||||
row=sar_row, column=1, sticky=tk.EW, padx=(0, 10), pady=1
|
||||
)
|
||||
|
||||
# SAR Brightness Slider
|
||||
self.contrast_scale.grid(row=sar_row, column=1, sticky=tk.EW, padx=(0, 10), pady=1)
|
||||
self.brightness_label = ttk.Label(self.sar_params_frame, text="Brightness:")
|
||||
self.brightness_label.grid(
|
||||
row=sar_row, column=2, sticky=tk.W, padx=(0, 2), pady=1
|
||||
)
|
||||
self.brightness_label.grid(row=sar_row, column=2, sticky=tk.W, padx=(0, 2), pady=1)
|
||||
self.brightness_scale = ttk.Scale(
|
||||
self.sar_params_frame,
|
||||
orient=tk.HORIZONTAL,
|
||||
from_=-100,
|
||||
to=100,
|
||||
value=self.app.state.sar_brightness, # Set from state
|
||||
command=self.app.update_brightness,
|
||||
self.sar_params_frame, orient=tk.HORIZONTAL, from_=-100, to=100,
|
||||
value=self.app.state.sar_brightness, command=self.app.update_brightness,
|
||||
)
|
||||
self.brightness_scale.grid(
|
||||
row=sar_row, column=3, sticky=tk.EW, padx=(0, 5), pady=1
|
||||
)
|
||||
|
||||
# SAR Metadata Checkbox (Still created here as it belongs logically with SAR params)
|
||||
self.brightness_scale.grid(row=sar_row, column=3, sticky=tk.EW, padx=(0, 5), pady=1)
|
||||
sar_row += 1
|
||||
# self.show_meta_var is already created in __init__
|
||||
self.show_meta_check = ttk.Checkbutton(
|
||||
self.sar_params_frame,
|
||||
text="Show SAR Metadata",
|
||||
variable=self.show_meta_var,
|
||||
command=self.app.toggle_sar_metadata_display, # Link to app callback
|
||||
self.sar_params_frame, text="Show SAR Metadata", variable=self.show_meta_var,
|
||||
command=self.app.toggle_sar_metadata_display,
|
||||
)
|
||||
self.show_meta_check.grid(
|
||||
row=sar_row, column=0, columnspan=4, sticky=tk.W, padx=5, pady=(5, 2)
|
||||
)
|
||||
|
||||
# Configure SAR frame column weights
|
||||
self.sar_params_frame.columnconfigure(1, weight=1) # Allow sliders to expand
|
||||
self.show_meta_check.grid(row=sar_row, column=0, columnspan=4, sticky=tk.W, padx=5, pady=(5, 2))
|
||||
self.sar_params_frame.columnconfigure(1, weight=1)
|
||||
self.sar_params_frame.columnconfigure(3, weight=1)
|
||||
|
||||
|
||||
# --- 2. MFD Parameters Frame ---
|
||||
# ... (Questa sezione rimane invariata) ...
|
||||
self.mfd_params_frame = ttk.Labelframe(self, text="MFD Parameters", padding=5)
|
||||
self.mfd_params_frame.pack(side=tk.TOP, fill=tk.X, padx=5, pady=2)
|
||||
|
||||
mfd_categories_ordered = [
|
||||
"Occlusion",
|
||||
"Cat A",
|
||||
"Cat B",
|
||||
"Cat C",
|
||||
"Cat C1",
|
||||
"Cat C2",
|
||||
"Cat C3",
|
||||
]
|
||||
mfd_categories_ordered = ["Occlusion", "Cat A", "Cat B", "Cat C", "Cat C1", "Cat C2", "Cat C3"]
|
||||
num_categories = len(mfd_categories_ordered)
|
||||
|
||||
for index, name in enumerate(mfd_categories_ordered):
|
||||
row = index // 2
|
||||
col_offset = 0 if (index % 2 == 0) else 4
|
||||
|
||||
# Category Label
|
||||
cat_label = ttk.Label(self.mfd_params_frame, text=f"{name}:")
|
||||
cat_label.grid(
|
||||
row=row, column=0 + col_offset, sticky=tk.W, padx=(5, 1), pady=1
|
||||
)
|
||||
|
||||
# Intensity Slider Variable and Widget
|
||||
cat_label.grid(row=row, column=0 + col_offset, sticky=tk.W, padx=(5,1), pady=1)
|
||||
intensity_var = tk.IntVar(value=config.DEFAULT_MFD_INTENSITY)
|
||||
try:
|
||||
intensity_var.set(
|
||||
self.app.state.mfd_params["categories"][name]["intensity"]
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
try: intensity_var.set(self.app.state.mfd_params["categories"][name]["intensity"])
|
||||
except Exception: pass
|
||||
intensity_scale = ttk.Scale(
|
||||
self.mfd_params_frame,
|
||||
orient=tk.HORIZONTAL,
|
||||
length=100,
|
||||
from_=0,
|
||||
to=255,
|
||||
variable=intensity_var,
|
||||
command=lambda v, n=name, var=intensity_var: (
|
||||
self.app.update_mfd_category_intensity(n, var.get())
|
||||
),
|
||||
self.mfd_params_frame, orient=tk.HORIZONTAL, length=100, from_=0, to=255, variable=intensity_var,
|
||||
command=lambda v, n=name, var=intensity_var: self.app.update_mfd_category_intensity(n, var.get()),
|
||||
)
|
||||
intensity_scale.grid(
|
||||
row=row, column=1 + col_offset, sticky=tk.EW, padx=1, pady=1
|
||||
)
|
||||
|
||||
# Color Chooser Button
|
||||
intensity_scale.grid(row=row, column=1 + col_offset, sticky=tk.EW, padx=1, pady=1)
|
||||
color_button = ttk.Button(
|
||||
self.mfd_params_frame,
|
||||
text="Color",
|
||||
width=5,
|
||||
command=lambda n=name: self.app.choose_mfd_category_color(n),
|
||||
)
|
||||
color_button.grid(
|
||||
row=row, column=2 + col_offset, sticky=tk.W, padx=1, pady=1
|
||||
)
|
||||
|
||||
# Color Preview Label
|
||||
color_label = tk.Label(
|
||||
self.mfd_params_frame, text="", width=3, relief=tk.SUNKEN, borderwidth=1
|
||||
self.mfd_params_frame, text="Color", width=5, command=lambda n=name: self.app.choose_mfd_category_color(n),
|
||||
)
|
||||
color_button.grid(row=row, column=2 + col_offset, sticky=tk.W, padx=1, pady=1)
|
||||
color_label = tk.Label(self.mfd_params_frame, text="", width=3, relief=tk.SUNKEN, borderwidth=1)
|
||||
try:
|
||||
bgr = self.app.state.mfd_params["categories"][name]["color"]
|
||||
hex_color = f"#{bgr[2]:02x}{bgr[1]:02x}{bgr[0]:02x}"
|
||||
color_label.config(background=hex_color)
|
||||
except Exception:
|
||||
color_label.config(background="grey")
|
||||
color_label.grid(
|
||||
row=row, column=3 + col_offset, sticky=tk.W, padx=(1, 5), pady=1
|
||||
)
|
||||
except Exception: color_label.config(background="grey")
|
||||
color_label.grid(row=row, column=3 + col_offset, sticky=tk.W, padx=(1,5), pady=1)
|
||||
self.mfd_color_labels[name] = color_label
|
||||
|
||||
# Raw Map Intensity Slider
|
||||
last_cat_row = (num_categories - 1) // 2
|
||||
last_cat_row = (num_categories -1) // 2
|
||||
raw_map_col_offset = 4 if (num_categories % 2 != 0) else 0
|
||||
raw_map_row = last_cat_row if (num_categories % 2 != 0) else last_cat_row + 1
|
||||
|
||||
raw_map_label = ttk.Label(self.mfd_params_frame, text="Raw Map:")
|
||||
raw_map_label.grid(
|
||||
row=raw_map_row,
|
||||
column=0 + raw_map_col_offset,
|
||||
sticky=tk.W,
|
||||
padx=(5, 1),
|
||||
pady=1,
|
||||
)
|
||||
|
||||
raw_map_label.grid(row=raw_map_row, column=0 + raw_map_col_offset, sticky=tk.W, padx=(5,1), pady=1)
|
||||
raw_map_intensity_var = tk.IntVar(value=config.DEFAULT_MFD_RAW_MAP_INTENSITY)
|
||||
try:
|
||||
raw_map_intensity_var.set(self.app.state.mfd_params["raw_map_intensity"])
|
||||
except Exception:
|
||||
pass
|
||||
self.mfd_raw_map_intensity_var = raw_map_intensity_var # Keep reference
|
||||
|
||||
try: raw_map_intensity_var.set(self.app.state.mfd_params["raw_map_intensity"])
|
||||
except Exception: pass
|
||||
self.mfd_raw_map_intensity_var = raw_map_intensity_var
|
||||
raw_map_scale = ttk.Scale(
|
||||
self.mfd_params_frame,
|
||||
orient=tk.HORIZONTAL,
|
||||
length=100,
|
||||
from_=0,
|
||||
to=255,
|
||||
variable=raw_map_intensity_var,
|
||||
command=lambda v: self.app.update_mfd_raw_map_intensity(
|
||||
raw_map_intensity_var.get()
|
||||
),
|
||||
self.mfd_params_frame, orient=tk.HORIZONTAL, length=100, from_=0, to=255, variable=raw_map_intensity_var,
|
||||
command=lambda v: self.app.update_mfd_raw_map_intensity(raw_map_intensity_var.get()),
|
||||
)
|
||||
raw_map_scale.grid(
|
||||
row=raw_map_row,
|
||||
column=1 + raw_map_col_offset,
|
||||
columnspan=3,
|
||||
sticky=tk.EW,
|
||||
padx=(1, 5),
|
||||
pady=1,
|
||||
)
|
||||
# Configure MFD frame column weights
|
||||
raw_map_scale.grid(row=raw_map_row, column=1 + raw_map_col_offset, columnspan=3, sticky=tk.EW, padx=(1,5), pady=1)
|
||||
self.mfd_params_frame.columnconfigure(1, weight=1)
|
||||
self.mfd_params_frame.columnconfigure(5, weight=1)
|
||||
|
||||
# --- 3. Map Parameters Frame ---
|
||||
# ... (Questa sezione rimane invariata) ...
|
||||
self.map_params_frame = ttk.Labelframe(self, text="Map Parameters", padding=5)
|
||||
self.map_params_frame.pack(side=tk.TOP, fill=tk.X, padx=5, pady=2)
|
||||
|
||||
map_row = 0 # Row counter for Map grid
|
||||
|
||||
# Map Size Combobox
|
||||
map_row = 0
|
||||
self.map_size_label = ttk.Label(self.map_params_frame, text="Map Display Size:")
|
||||
self.map_size_label.grid(
|
||||
row=map_row, column=0, sticky=tk.W, padx=(5, 2), pady=1
|
||||
)
|
||||
self.map_size_label.grid(row=map_row, column=0, sticky=tk.W, padx=(5,2), pady=1)
|
||||
self.map_size_combo = ttk.Combobox(
|
||||
self.map_params_frame,
|
||||
values=config.MAP_SIZE_FACTORS,
|
||||
state="readonly",
|
||||
width=6,
|
||||
self.map_params_frame, values=config.MAP_SIZE_FACTORS, state="readonly", width=6,
|
||||
)
|
||||
self.map_size_combo.set(config.DEFAULT_MAP_SIZE)
|
||||
self.map_size_combo.grid(
|
||||
row=map_row, column=1, sticky=tk.EW, padx=(2, 10), pady=1
|
||||
)
|
||||
self.map_size_combo.grid(row=map_row, column=1, sticky=tk.EW, padx=(2,10), pady=1)
|
||||
self.map_size_combo.bind("<<ComboboxSelected>>", self.app.update_map_size)
|
||||
|
||||
# Save Map Button
|
||||
self.save_map_button = ttk.Button(
|
||||
self.map_params_frame,
|
||||
text="Save Map View",
|
||||
command=self.app.save_current_map_view,
|
||||
self.map_params_frame, text="Save Map View", command=self.app.save_current_map_view,
|
||||
)
|
||||
self.save_map_button.grid(
|
||||
row=map_row, column=2, columnspan=4, sticky=tk.E, padx=5, pady=1
|
||||
)
|
||||
|
||||
self.save_map_button.grid(row=map_row, column=2, columnspan=4, sticky=tk.E, padx=5, pady=1)
|
||||
map_row += 1
|
||||
|
||||
# SAR Overlay Checkbox
|
||||
self.sar_overlay_var = tk.BooleanVar(
|
||||
value=self.app.state.map_sar_overlay_enabled
|
||||
)
|
||||
self.sar_overlay_var = tk.BooleanVar(value=self.app.state.map_sar_overlay_enabled)
|
||||
self.sar_overlay_check = ttk.Checkbutton(
|
||||
self.map_params_frame,
|
||||
text="Show SAR Overlay on Map",
|
||||
variable=self.sar_overlay_var,
|
||||
self.map_params_frame, text="Show SAR Overlay on Map", variable=self.sar_overlay_var,
|
||||
command=self.app.toggle_sar_overlay,
|
||||
)
|
||||
self.sar_overlay_check.grid(
|
||||
row=map_row, column=0, columnspan=2, sticky=tk.W, padx=5, pady=2
|
||||
)
|
||||
|
||||
self.sar_overlay_check.grid(row=map_row, column=0, columnspan=2, sticky=tk.W, padx=5, pady=2)
|
||||
map_row += 1
|
||||
|
||||
# SAR Overlay Alpha Slider
|
||||
self.alpha_label = ttk.Label(self.map_params_frame, text="SAR Overlay Alpha:")
|
||||
self.alpha_label.grid(row=map_row, column=0, sticky=tk.W, padx=(5, 2), pady=1)
|
||||
self.sar_overlay_alpha_var = tk.DoubleVar(
|
||||
value=self.app.state.map_sar_overlay_alpha
|
||||
)
|
||||
self.alpha_label.grid(row=map_row, column=0, sticky=tk.W, padx=(5,2), pady=1)
|
||||
self.sar_overlay_alpha_var = tk.DoubleVar(value=self.app.state.map_sar_overlay_alpha)
|
||||
self.alpha_scale = ttk.Scale(
|
||||
self.map_params_frame,
|
||||
orient=tk.HORIZONTAL,
|
||||
from_=0.0,
|
||||
to=1.0,
|
||||
variable=self.sar_overlay_alpha_var,
|
||||
self.map_params_frame, orient=tk.HORIZONTAL, from_=0.0, to=1.0, variable=self.sar_overlay_alpha_var,
|
||||
)
|
||||
self.alpha_scale.bind("<ButtonRelease-1>", self.app.on_alpha_slider_release)
|
||||
self.alpha_scale.grid(
|
||||
row=map_row, column=1, columnspan=5, sticky=tk.EW, padx=(0, 5), pady=1
|
||||
)
|
||||
|
||||
self.alpha_scale.grid(row=map_row, column=1, columnspan=5, sticky=tk.EW, padx=(0,5), pady=1)
|
||||
map_row += 1
|
||||
|
||||
# SAR Shift Inputs and Apply Button
|
||||
shift_label = ttk.Label(self.map_params_frame, text="SAR Shift (deg):")
|
||||
shift_label.grid(row=map_row, column=0, sticky=tk.W, padx=(5, 2), pady=1)
|
||||
shift_label.grid(row=map_row, column=0, sticky=tk.W, padx=(5,2), pady=1)
|
||||
lat_label = ttk.Label(self.map_params_frame, text="Lat:")
|
||||
lat_label.grid(row=map_row, column=1, sticky=tk.W, padx=(0, 0), pady=1)
|
||||
lat_label.grid(row=map_row, column=1, sticky=tk.W, padx=(0,0), pady=1)
|
||||
self.lat_shift_entry = ttk.Entry(
|
||||
self.map_params_frame, textvariable=self.sar_lat_shift_var, width=10
|
||||
)
|
||||
self.lat_shift_entry.grid(
|
||||
row=map_row, column=2, sticky=tk.W, padx=(0, 5), pady=1
|
||||
)
|
||||
self.lat_shift_entry.grid(row=map_row, column=2, sticky=tk.W, padx=(0,5), pady=1)
|
||||
lon_label = ttk.Label(self.map_params_frame, text="Lon:")
|
||||
lon_label.grid(row=map_row, column=3, sticky=tk.W, padx=(5, 0), pady=1)
|
||||
lon_label.grid(row=map_row, column=3, sticky=tk.W, padx=(5,0), pady=1)
|
||||
self.lon_shift_entry = ttk.Entry(
|
||||
self.map_params_frame, textvariable=self.sar_lon_shift_var, width=10
|
||||
)
|
||||
self.lon_shift_entry.grid(
|
||||
row=map_row, column=4, sticky=tk.W, padx=(0, 5), pady=1
|
||||
)
|
||||
self.lon_shift_entry.grid(row=map_row, column=4, sticky=tk.W, padx=(0,5), pady=1)
|
||||
self.apply_shift_button = ttk.Button(
|
||||
self.map_params_frame,
|
||||
text="Apply Shift",
|
||||
command=self.app.apply_sar_overlay_shift,
|
||||
self.map_params_frame, text="Apply Shift", command=self.app.apply_sar_overlay_shift,
|
||||
)
|
||||
self.apply_shift_button.grid(
|
||||
row=map_row, column=5, sticky=tk.E, padx=(5, 5), pady=1
|
||||
)
|
||||
|
||||
# Configure Map frame column weights
|
||||
self.apply_shift_button.grid(row=map_row, column=5, sticky=tk.E, padx=(5,5), pady=1)
|
||||
self.map_params_frame.columnconfigure(2, weight=1)
|
||||
self.map_params_frame.columnconfigure(4, weight=1)
|
||||
|
||||
# --- 4. Info Display Frame ---
|
||||
# --- 4. Info Display Frame (MODIFIED) ---
|
||||
self.info_display_frame = ttk.Labelframe(self, text="Info Display", padding=5)
|
||||
self.info_display_frame.pack(side=tk.TOP, fill=tk.X, padx=5, pady=2)
|
||||
|
||||
info_row = 0 # Row counter for Info grid
|
||||
button_width = 3 # Standard width for Go/GE buttons
|
||||
info_row = 0
|
||||
button_width = 3
|
||||
entry_width_coords = 30 # Adjusted width for coords + elevation
|
||||
entry_width_orient = 10
|
||||
entry_width_size = 18
|
||||
entry_width_stats_drop = 28
|
||||
entry_width_stats_incmpl = 15
|
||||
entry_width_elevation = 15 # Width for elevation labels
|
||||
|
||||
# --- Row 0: SAR Center Coords ---
|
||||
# --- Row 0: SAR Center Coords & Elevation ---
|
||||
ref_label = ttk.Label(self.info_display_frame, text="SAR Center:")
|
||||
ref_label.grid(row=info_row, column=0, sticky=tk.W, padx=(5, 2), pady=1)
|
||||
self.sar_center_entry = ttk.Entry(
|
||||
self.info_display_frame,
|
||||
textvariable=self.sar_center_coords_var,
|
||||
state="readonly",
|
||||
width=35,
|
||||
self.info_display_frame, textvariable=self.sar_center_coords_var,
|
||||
state="readonly", width=entry_width_coords
|
||||
)
|
||||
self.sar_center_entry.grid(
|
||||
row=info_row, column=1, columnspan=3, sticky=tk.EW, padx=(0, 2), pady=1
|
||||
self.sar_center_entry.grid(row=info_row, column=1, columnspan=2, sticky=tk.EW, padx=(0, 2), pady=1)
|
||||
|
||||
# --- >>> NEW: SAR Center Elevation Label <<< ---
|
||||
self.sar_center_elev_label = ttk.Label(
|
||||
self.info_display_frame, textvariable=self.sar_center_elevation_var,
|
||||
width=entry_width_elevation, anchor=tk.W, relief=tk.SUNKEN, borderwidth=1
|
||||
)
|
||||
self.sar_center_elev_label.grid(row=info_row, column=3, sticky=tk.EW, padx=(2,2), pady=1)
|
||||
# --- >>> END NEW <<< ---
|
||||
|
||||
self.ref_gmaps_button = ttk.Button(
|
||||
self.info_display_frame,
|
||||
text="Go",
|
||||
width=button_width,
|
||||
command=lambda: self.app.go_to_google_maps("sar_center"),
|
||||
)
|
||||
self.ref_gmaps_button.grid(
|
||||
row=info_row, column=4, sticky=tk.E, padx=(0, 1), pady=1
|
||||
self.info_display_frame, text="Go", width=button_width,
|
||||
command=lambda: self.app.go_to_google_maps("sar_center")
|
||||
)
|
||||
self.ref_gmaps_button.grid(row=info_row, column=4, sticky=tk.E, padx=(0, 1), pady=1)
|
||||
self.ref_gearth_button = ttk.Button(
|
||||
self.info_display_frame,
|
||||
text="GE",
|
||||
width=button_width,
|
||||
command=lambda: self.app.go_to_google_earth("sar_center"),
|
||||
)
|
||||
self.ref_gearth_button.grid(
|
||||
row=info_row, column=5, sticky=tk.E, padx=(0, 5), pady=1
|
||||
self.info_display_frame, text="GE", width=button_width,
|
||||
command=lambda: self.app.go_to_google_earth("sar_center")
|
||||
)
|
||||
self.ref_gearth_button.grid(row=info_row, column=5, sticky=tk.E, padx=(0, 5), pady=1)
|
||||
info_row += 1
|
||||
|
||||
# --- Row 1: Image Orient & Image Size ---
|
||||
orient_label = ttk.Label(self.info_display_frame, text="Image Orient:")
|
||||
orient_label.grid(row=info_row, column=0, sticky=tk.W, padx=5, pady=1)
|
||||
self.sar_orientation_entry = ttk.Entry(
|
||||
self.info_display_frame,
|
||||
textvariable=self.sar_orientation_var,
|
||||
state="readonly",
|
||||
width=15,
|
||||
)
|
||||
self.sar_orientation_entry.grid(
|
||||
row=info_row, column=1, sticky=tk.EW, padx=(0, 5), pady=1
|
||||
self.info_display_frame, textvariable=self.sar_orientation_var,
|
||||
state="readonly", width=entry_width_orient
|
||||
)
|
||||
self.sar_orientation_entry.grid(row=info_row, column=1, sticky=tk.EW, padx=(0, 5), pady=1)
|
||||
|
||||
size_label = ttk.Label(self.info_display_frame, text="Image Size:")
|
||||
size_label.grid(row=info_row, column=2, sticky=tk.W, padx=(10, 2), pady=1)
|
||||
size_label.grid(row=info_row, column=2, sticky=tk.W, padx=(5, 2), pady=1) # Adjusted column
|
||||
self.sar_size_entry = ttk.Entry(
|
||||
self.info_display_frame,
|
||||
textvariable=self.sar_size_km_var,
|
||||
state="readonly",
|
||||
width=25,
|
||||
)
|
||||
self.sar_size_entry.grid(
|
||||
row=info_row, column=3, columnspan=3, sticky=tk.EW, padx=(0, 5), pady=1
|
||||
self.info_display_frame, textvariable=self.sar_size_km_var,
|
||||
state="readonly", width=entry_width_size
|
||||
)
|
||||
# Span across remaining columns for Go/GE alignment
|
||||
self.sar_size_entry.grid(row=info_row, column=3, columnspan=3, sticky=tk.EW, padx=(0, 5), pady=1)
|
||||
info_row += 1
|
||||
|
||||
# --- Row 2: SAR Mouse Coords ---
|
||||
# --- Row 2: SAR Mouse Coords & Elevation ---
|
||||
sar_mouse_label = ttk.Label(self.info_display_frame, text="SAR Mouse:")
|
||||
sar_mouse_label.grid(row=info_row, column=0, sticky=tk.W, padx=5, pady=1)
|
||||
self.mouse_latlon_entry = ttk.Entry(
|
||||
self.info_display_frame,
|
||||
textvariable=self.mouse_coords_var,
|
||||
state="readonly",
|
||||
width=35,
|
||||
self.info_display_frame, textvariable=self.mouse_coords_var,
|
||||
state="readonly", width=entry_width_coords
|
||||
)
|
||||
self.mouse_latlon_entry.grid(
|
||||
row=info_row, column=1, columnspan=3, sticky=tk.EW, padx=(0, 2), pady=1
|
||||
self.mouse_latlon_entry.grid(row=info_row, column=1, columnspan=2, sticky=tk.EW, padx=(0, 2), pady=1)
|
||||
|
||||
# --- >>> NEW: SAR Mouse Elevation Label <<< ---
|
||||
self.sar_mouse_elev_label = ttk.Label(
|
||||
self.info_display_frame, textvariable=self.sar_mouse_elevation_var,
|
||||
width=entry_width_elevation, anchor=tk.W, relief=tk.SUNKEN, borderwidth=1
|
||||
)
|
||||
self.sar_mouse_elev_label.grid(row=info_row, column=3, sticky=tk.EW, padx=(2,2), pady=1)
|
||||
# --- >>> END NEW <<< ---
|
||||
|
||||
self.sar_mouse_gmaps_button = ttk.Button(
|
||||
self.info_display_frame,
|
||||
text="Go",
|
||||
width=button_width,
|
||||
command=lambda: self.app.go_to_google_maps("sar_mouse"),
|
||||
)
|
||||
self.sar_mouse_gmaps_button.grid(
|
||||
row=info_row, column=4, sticky=tk.E, padx=(0, 1), pady=1
|
||||
self.info_display_frame, text="Go", width=button_width,
|
||||
command=lambda: self.app.go_to_google_maps("sar_mouse")
|
||||
)
|
||||
self.sar_mouse_gmaps_button.grid(row=info_row, column=4, sticky=tk.E, padx=(0, 1), pady=1)
|
||||
self.sar_mouse_gearth_button = ttk.Button(
|
||||
self.info_display_frame,
|
||||
text="GE",
|
||||
width=button_width,
|
||||
command=lambda: self.app.go_to_google_earth("sar_mouse"),
|
||||
)
|
||||
self.sar_mouse_gearth_button.grid(
|
||||
row=info_row, column=5, sticky=tk.E, padx=(0, 5), pady=1
|
||||
self.info_display_frame, text="GE", width=button_width,
|
||||
command=lambda: self.app.go_to_google_earth("sar_mouse")
|
||||
)
|
||||
self.sar_mouse_gearth_button.grid(row=info_row, column=5, sticky=tk.E, padx=(0, 5), pady=1)
|
||||
info_row += 1
|
||||
|
||||
# --- Row 3: Map Mouse Coords ---
|
||||
# --- Row 3: Map Mouse Coords & Elevation ---
|
||||
map_mouse_label = ttk.Label(self.info_display_frame, text="Map Mouse:")
|
||||
map_mouse_label.grid(row=info_row, column=0, sticky=tk.W, padx=5, pady=1)
|
||||
self.map_mouse_latlon_entry = ttk.Entry(
|
||||
self.info_display_frame,
|
||||
textvariable=self.map_mouse_coords_var,
|
||||
state="readonly",
|
||||
width=35,
|
||||
self.info_display_frame, textvariable=self.map_mouse_coords_var,
|
||||
state="readonly", width=entry_width_coords
|
||||
)
|
||||
self.map_mouse_latlon_entry.grid(
|
||||
row=info_row, column=1, columnspan=3, sticky=tk.EW, padx=(0, 2), pady=1
|
||||
self.map_mouse_latlon_entry.grid(row=info_row, column=1, columnspan=2, sticky=tk.EW, padx=(0, 2), pady=1)
|
||||
|
||||
# --- >>> NEW: Map Mouse Elevation Label <<< ---
|
||||
self.map_mouse_elev_label = ttk.Label(
|
||||
self.info_display_frame, textvariable=self.map_mouse_elevation_var,
|
||||
width=entry_width_elevation, anchor=tk.W, relief=tk.SUNKEN, borderwidth=1
|
||||
)
|
||||
self.map_mouse_elev_label.grid(row=info_row, column=3, sticky=tk.EW, padx=(2,2), pady=1)
|
||||
# --- >>> END NEW <<< ---
|
||||
|
||||
self.map_mouse_gmaps_button = ttk.Button(
|
||||
self.info_display_frame,
|
||||
text="Go",
|
||||
width=button_width,
|
||||
command=lambda: self.app.go_to_google_maps("map_mouse"),
|
||||
)
|
||||
self.map_mouse_gmaps_button.grid(
|
||||
row=info_row, column=4, sticky=tk.E, padx=(0, 1), pady=1
|
||||
self.info_display_frame, text="Go", width=button_width,
|
||||
command=lambda: self.app.go_to_google_maps("map_mouse")
|
||||
)
|
||||
self.map_mouse_gmaps_button.grid(row=info_row, column=4, sticky=tk.E, padx=(0, 1), pady=1)
|
||||
self.map_mouse_gearth_button = ttk.Button(
|
||||
self.info_display_frame,
|
||||
text="GE",
|
||||
width=button_width,
|
||||
command=lambda: self.app.go_to_google_earth("map_mouse"),
|
||||
)
|
||||
self.map_mouse_gearth_button.grid(
|
||||
row=info_row, column=5, sticky=tk.E, padx=(0, 5), pady=1
|
||||
self.info_display_frame, text="GE", width=button_width,
|
||||
command=lambda: self.app.go_to_google_earth("map_mouse")
|
||||
)
|
||||
self.map_mouse_gearth_button.grid(row=info_row, column=5, sticky=tk.E, padx=(0, 5), pady=1)
|
||||
info_row += 1
|
||||
|
||||
# --- Row 4: Drop & Incomplete Stats ---
|
||||
dropped_label_text = ttk.Label(self.info_display_frame, text="Stats Drop:")
|
||||
dropped_label_text.grid(row=info_row, column=0, sticky=tk.W, padx=5, pady=1)
|
||||
self.dropped_entry = ttk.Entry(
|
||||
self.info_display_frame,
|
||||
textvariable=self.dropped_stats_var,
|
||||
state="readonly",
|
||||
width=30,
|
||||
)
|
||||
self.dropped_entry.grid(
|
||||
row=info_row, column=1, sticky=tk.EW, padx=(0, 5), pady=1
|
||||
self.info_display_frame, textvariable=self.dropped_stats_var,
|
||||
state="readonly", width=entry_width_stats_drop
|
||||
)
|
||||
self.dropped_entry.grid(row=info_row, column=1, columnspan=2, sticky=tk.EW, padx=(0, 5), pady=1) # Span 2 cols
|
||||
|
||||
incomplete_label_text = ttk.Label(self.info_display_frame, text="Incomplete:")
|
||||
incomplete_label_text.grid(
|
||||
row=info_row, column=2, sticky=tk.W, padx=(10, 2), pady=1
|
||||
)
|
||||
incomplete_label_text.grid(row=info_row, column=3, sticky=tk.W, padx=(5, 2), pady=1) # Start at col 3
|
||||
self.incomplete_entry = ttk.Entry(
|
||||
self.info_display_frame,
|
||||
textvariable=self.incomplete_stats_var,
|
||||
state="readonly",
|
||||
width=15,
|
||||
)
|
||||
self.incomplete_entry.grid(
|
||||
row=info_row, column=3, columnspan=3, sticky=tk.EW, padx=(0, 5), pady=1
|
||||
self.info_display_frame, textvariable=self.incomplete_stats_var,
|
||||
state="readonly", width=entry_width_stats_incmpl
|
||||
)
|
||||
self.incomplete_entry.grid(row=info_row, column=4, columnspan=2, sticky=tk.EW, padx=(0, 5), pady=1) # Span 2 cols
|
||||
info_row += 1
|
||||
|
||||
# --- Row 5: "GE All" Button ---
|
||||
@ -594,85 +397,78 @@ class ControlPanel(ttk.Frame): # This is the main panel holding user controls
|
||||
)
|
||||
|
||||
# Configure column weights for Info Display frame
|
||||
self.info_display_frame.columnconfigure(1, weight=1) # Entry column
|
||||
self.info_display_frame.columnconfigure(3, weight=1) # Entry column
|
||||
self.info_display_frame.columnconfigure(4, weight=0) # Button Go
|
||||
self.info_display_frame.columnconfigure(5, weight=0) # Button GE
|
||||
logging.debug(f"{log_prefix} Info Display frame created.")
|
||||
self.info_display_frame.columnconfigure(1, weight=1) # Coords entry
|
||||
self.info_display_frame.columnconfigure(2, weight=1) # Coords entry (continued for span) / Size Label
|
||||
self.info_display_frame.columnconfigure(3, weight=0) # Elevation Label / Size Entry / Incomplete Label
|
||||
self.info_display_frame.columnconfigure(4, weight=0) # Go Button / Size Entry / Incomplete Entry
|
||||
self.info_display_frame.columnconfigure(5, weight=0) # GE Button / Size Entry / Incomplete Entry
|
||||
|
||||
# --- 5. Metadata Display Frame (Creation REMOVED from here) ---
|
||||
# This is now created and managed in ControlPanelApp
|
||||
logging.debug(f"{log_prefix} Info Display frame created with elevation labels.")
|
||||
|
||||
# --- End of init_ui ---
|
||||
logging.debug(f"{log_prefix} init_ui widget creation complete.")
|
||||
|
||||
# --- UI Update Methods ---
|
||||
# ... (metodi set_sar_center_coords, set_mouse_coordinates, ecc. rimangono invariati) ...
|
||||
def set_sar_center_coords(self, latitude_str: str, longitude_str: str):
|
||||
"""Updates the SAR Center coordinates display."""
|
||||
text = f"Lat={latitude_str}, Lon={longitude_str}"
|
||||
try:
|
||||
self.sar_center_coords_var.set(text)
|
||||
except Exception as e:
|
||||
logging.warning(f"[UI Update] Error setting SAR center coords: {e}")
|
||||
try: self.sar_center_coords_var.set(text)
|
||||
except Exception as e: logging.warning(f"[UI Update] Error setting SAR center coords: {e}")
|
||||
|
||||
def set_mouse_coordinates(self, latitude_str: str, longitude_str: str):
|
||||
"""Updates the SAR Mouse coordinates display."""
|
||||
text = f"Lat={latitude_str}, Lon={longitude_str}"
|
||||
try:
|
||||
self.mouse_coords_var.set(text)
|
||||
except Exception as e:
|
||||
logging.warning(f"[UI Update] Error setting SAR mouse coords: {e}")
|
||||
try: self.mouse_coords_var.set(text)
|
||||
except Exception as e: logging.warning(f"[UI Update] Error setting SAR mouse coords: {e}")
|
||||
|
||||
def set_map_mouse_coordinates(self, latitude_str: str, longitude_str: str):
|
||||
"""Updates the Map Mouse coordinates display."""
|
||||
text = f"Lat={latitude_str}, Lon={longitude_str}"
|
||||
try:
|
||||
self.map_mouse_coords_var.set(text)
|
||||
except Exception as e:
|
||||
logging.warning(f"[UI Update] Error setting Map mouse coords: {e}")
|
||||
try: self.map_mouse_coords_var.set(text)
|
||||
except Exception as e: logging.warning(f"[UI Update] Error setting Map mouse coords: {e}")
|
||||
|
||||
def set_sar_orientation(self, orientation_deg_str: str):
|
||||
"""Updates the SAR Orientation display."""
|
||||
try:
|
||||
self.sar_orientation_var.set(orientation_deg_str)
|
||||
except Exception as e:
|
||||
logging.warning(f"[UI Update] Error setting SAR orientation: {e}")
|
||||
try: self.sar_orientation_var.set(orientation_deg_str)
|
||||
except Exception as e: logging.warning(f"[UI Update] Error setting SAR orientation: {e}")
|
||||
|
||||
def set_sar_size_km(self, size_text: str):
|
||||
"""Updates the SAR Size display."""
|
||||
try:
|
||||
self.sar_size_km_var.set(size_text)
|
||||
except Exception as e:
|
||||
logging.warning(f"[UI Update] Error setting SAR size: {e}")
|
||||
try: self.sar_size_km_var.set(size_text)
|
||||
except Exception as e: logging.warning(f"[UI Update] Error setting SAR size: {e}")
|
||||
|
||||
def set_statistics_display(self, dropped_text: str, incomplete_text: str):
|
||||
"""Updates the statistics display fields."""
|
||||
try:
|
||||
self.dropped_stats_var.set(dropped_text)
|
||||
self.incomplete_stats_var.set(incomplete_text)
|
||||
except Exception as e:
|
||||
logging.warning(f"[UI Update] Error setting stats display: {e}")
|
||||
except Exception as e: logging.warning(f"[UI Update] Error setting stats display: {e}")
|
||||
|
||||
def update_mfd_color_display(
|
||||
self, category_name: str, color_bgr_tuple: Tuple[int, int, int]
|
||||
):
|
||||
"""Updates the background color of an MFD category preview label."""
|
||||
def update_mfd_color_display(self, category_name: str, color_bgr_tuple: Tuple[int, int, int]):
|
||||
if category_name in self.mfd_color_labels:
|
||||
lbl = self.mfd_color_labels[category_name]
|
||||
# Format BGR tuple to hex color string #RRGGBB
|
||||
hex_color = (
|
||||
f"#{color_bgr_tuple[2]:02x}"
|
||||
f"{color_bgr_tuple[1]:02x}"
|
||||
f"{color_bgr_tuple[0]:02x}"
|
||||
)
|
||||
hex_color = f"#{color_bgr_tuple[2]:02x}{color_bgr_tuple[1]:02x}{color_bgr_tuple[0]:02x}"
|
||||
try:
|
||||
# Update label background if it still exists
|
||||
if lbl.winfo_exists():
|
||||
lbl.config(background=hex_color)
|
||||
except Exception as e:
|
||||
logging.exception(
|
||||
f"[UI Update] Error updating MFD color for {category_name}: {e}"
|
||||
)
|
||||
if lbl.winfo_exists(): lbl.config(background=hex_color)
|
||||
except Exception as e: logging.exception(f"[UI Update] Error updating MFD color for {category_name}: {e}")
|
||||
|
||||
def set_sar_center_elevation(self, elevation_text: str):
|
||||
"""Updates the SAR Center elevation display label."""
|
||||
try:
|
||||
# The text is now pre-formatted by app_main.py
|
||||
self.sar_center_elevation_var.set(elevation_text)
|
||||
except Exception as e:
|
||||
logging.warning(f"[UI Update] Error setting SAR center elevation: {e}")
|
||||
|
||||
def set_sar_mouse_elevation(self, elevation_text: str):
|
||||
"""Updates the SAR Mouse elevation display label."""
|
||||
try:
|
||||
# The text is now pre-formatted by app_main.py
|
||||
self.sar_mouse_elevation_var.set(elevation_text)
|
||||
except Exception as e:
|
||||
logging.warning(f"[UI Update] Error setting SAR mouse elevation: {e}")
|
||||
|
||||
def set_map_mouse_elevation(self, elevation_text: str):
|
||||
"""Updates the Map Mouse elevation display label."""
|
||||
try:
|
||||
# The text is now pre-formatted by app_main.py
|
||||
self.map_mouse_elevation_var.set(elevation_text)
|
||||
except Exception as e:
|
||||
logging.warning(f"[UI Update] Error setting Map mouse elevation: {e}")
|
||||
|
||||
|
||||
# --- StatusBar Class ---
|
||||
|
||||
Loading…
Reference in New Issue
Block a user