465 lines
24 KiB
Python
465 lines
24 KiB
Python
# elevation_gui.py
|
|
|
|
import tkinter as tk
|
|
from tkinter import ttk, messagebox, font as tkfont
|
|
import logging
|
|
import math
|
|
import threading
|
|
import os # Necessario per path.basename
|
|
from typing import Optional, Tuple
|
|
|
|
# === NUOVI IMPORT ===
|
|
from PIL import Image, ImageTk, ImageDraw, ImageFont
|
|
|
|
# Import the manager class
|
|
from elevation_manager import ElevationManager
|
|
|
|
# Configure logging
|
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
|
|
# Default directory for tiles
|
|
DEFAULT_TILE_DIR = "map_elevation"
|
|
# Costanti per la visualizzazione
|
|
CANVAS_BG_COLOR = "gray80"
|
|
TILE_BORDER_COLOR = "red"
|
|
TILE_TEXT_COLOR = "white"
|
|
TILE_TEXT_BG_COLOR = "black" # Sfondo semi-trasparente per il testo? Più complesso.
|
|
PLACEHOLDER_COLOR = "gray50"
|
|
|
|
|
|
class ElevationApp:
|
|
"""
|
|
Tkinter GUI application to get elevation data and pre-download DEM tiles for areas.
|
|
Displays browse images for downloaded tiles.
|
|
"""
|
|
def __init__(self, parent_widget: tk.Tk, elevation_manager: ElevationManager):
|
|
self.root = parent_widget
|
|
self.elevation_manager = elevation_manager
|
|
self.root.title("Elevation Finder & Downloader")
|
|
self.root.minsize(450, 650) # Aumenta altezza minima per canvas
|
|
|
|
# Memorizza le coordinate dell'ultima area richiesta per il display
|
|
self.last_area_coords: Optional[Tuple[float, float, float, float]] = None
|
|
# Memorizza riferimento all'oggetto PhotoImage per evitare garbage collection
|
|
self.current_photo_image: Optional[ImageTk.PhotoImage] = None
|
|
self.tile_images_cache = {} # Cache per le immagini PIL caricate
|
|
|
|
# --- Main Frame ---
|
|
main_frame = ttk.Frame(self.root, padding="10")
|
|
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
|
self.root.columnconfigure(0, weight=1)
|
|
self.root.rowconfigure(0, weight=1) # Permetti al main_frame di espandersi
|
|
|
|
# --- Sezioni Input e Download (come prima) ---
|
|
point_frame = ttk.LabelFrame(main_frame, text="Get Elevation for Point", padding="10")
|
|
point_frame.grid(row=0, column=0, padx=5, pady=5, sticky=(tk.W, tk.E))
|
|
point_frame.columnconfigure(1, weight=1)
|
|
# ... (widget Lat, Lon, Button, Result Label come prima) ...
|
|
ttk.Label(point_frame, text="Latitude:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=3)
|
|
self.lat_entry = ttk.Entry(point_frame, width=15)
|
|
self.lat_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=5, pady=3)
|
|
self.lat_entry.insert(0, "45.0")
|
|
ttk.Label(point_frame, text="Longitude:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=3)
|
|
self.lon_entry = ttk.Entry(point_frame, width=15)
|
|
self.lon_entry.grid(row=1, column=1, sticky=(tk.W, tk.E), padx=5, pady=3)
|
|
self.lon_entry.insert(0, "7.0")
|
|
self.get_elevation_button = ttk.Button(
|
|
point_frame, text="Get Elevation", command=self.calculate_single_elevation
|
|
)
|
|
self.get_elevation_button.grid(row=2, column=0, columnspan=2, pady=10, sticky=(tk.W, tk.E))
|
|
self.result_label = ttk.Label(point_frame, text="Result: ", wraplength=400, justify=tk.LEFT)
|
|
self.result_label.grid(row=3, column=0, columnspan=2, sticky=tk.W, pady=5)
|
|
|
|
|
|
area_frame = ttk.LabelFrame(main_frame, text="Pre-Download Tiles for Area", padding="10")
|
|
area_frame.grid(row=1, column=0, padx=5, pady=5, sticky=(tk.W, tk.E))
|
|
area_frame.columnconfigure(1, weight=1)
|
|
area_frame.columnconfigure(3, weight=1)
|
|
# ... (widget Min/Max Lat/Lon, Button, Status Label come prima) ...
|
|
ttk.Label(area_frame, text="Min Lat:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=3)
|
|
self.min_lat_entry = ttk.Entry(area_frame, width=10)
|
|
self.min_lat_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=5, pady=3)
|
|
self.min_lat_entry.insert(0, "44.0")
|
|
ttk.Label(area_frame, text="Max Lat:").grid(row=0, column=2, sticky=tk.W, padx=(10, 5), pady=3)
|
|
self.max_lat_entry = ttk.Entry(area_frame, width=10)
|
|
self.max_lat_entry.grid(row=0, column=3, sticky=(tk.W, tk.E), padx=5, pady=3)
|
|
self.max_lat_entry.insert(0, "46.0")
|
|
ttk.Label(area_frame, text="Min Lon:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=3)
|
|
self.min_lon_entry = ttk.Entry(area_frame, width=10)
|
|
self.min_lon_entry.grid(row=1, column=1, sticky=(tk.W, tk.E), padx=5, pady=3)
|
|
self.min_lon_entry.insert(0, "6.0")
|
|
ttk.Label(area_frame, text="Max Lon:").grid(row=1, column=2, sticky=tk.W, padx=(10, 5), pady=3)
|
|
self.max_lon_entry = ttk.Entry(area_frame, width=10)
|
|
self.max_lon_entry.grid(row=1, column=3, sticky=(tk.W, tk.E), padx=5, pady=3)
|
|
self.max_lon_entry.insert(0, "8.0")
|
|
self.download_area_button = ttk.Button(
|
|
area_frame, text="Download Area Tiles", command=self.start_area_download_thread
|
|
)
|
|
self.download_area_button.grid(row=2, column=0, columnspan=4, pady=10, sticky=(tk.W, tk.E))
|
|
self.download_status_label = ttk.Label(area_frame, text="Status: Idle", wraplength=400, justify=tk.LEFT)
|
|
self.download_status_label.grid(row=3, column=0, columnspan=4, sticky=tk.W, pady=5)
|
|
|
|
|
|
# --- NUOVA Sezione: Visualizzazione Immagine ---
|
|
image_frame = ttk.LabelFrame(main_frame, text="Browse Image Preview", padding="10")
|
|
# sticky='nsew' permette al frame di espandersi con la finestra
|
|
image_frame.grid(row=2, column=0, padx=5, pady=10, sticky=(tk.W, tk.E, tk.N, tk.S))
|
|
# Configura il frame immagine per espandersi
|
|
image_frame.columnconfigure(0, weight=1)
|
|
image_frame.rowconfigure(0, weight=1)
|
|
|
|
# Crea il Canvas per l'immagine
|
|
# Aggiungi delle scrollbar se l'immagine è grande? Per ora no.
|
|
self.image_canvas = tk.Canvas(image_frame, bg=CANVAS_BG_COLOR, width=400, height=300) # Dimensioni iniziali
|
|
self.image_canvas.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
|
|
|
# Configura la riga/colonna del main_frame per dare spazio al canvas
|
|
main_frame.rowconfigure(2, weight=1)
|
|
main_frame.columnconfigure(0, weight=1)
|
|
|
|
# Prova a caricare un font per le etichette
|
|
try:
|
|
self.label_font = ImageFont.truetype("arial.ttf", 10) # Prova font comune
|
|
except IOError:
|
|
self.label_font = ImageFont.load_default() # Fallback
|
|
logging.warning("Arial font not found, using default PIL font.")
|
|
|
|
|
|
def _clear_canvas(self):
|
|
"""Pulisce il canvas e resetta l'immagine corrente."""
|
|
self.image_canvas.delete("all")
|
|
self.current_photo_image = None
|
|
self.tile_images_cache.clear() # Svuota cache immagini
|
|
|
|
|
|
def _validate_coordinates(self, lat_str: str, lon_str: str) -> Tuple[float, float]:
|
|
"""Helper to validate single point coordinates."""
|
|
try:
|
|
if not lat_str: raise ValueError("Latitude cannot be empty.")
|
|
lat = float(lat_str)
|
|
if not (-90 <= lat < 90): raise ValueError("Latitude must be between -90 and < 90.")
|
|
if not lon_str: raise ValueError("Longitude cannot be empty.")
|
|
lon = float(lon_str)
|
|
if not (-180 <= lon < 180): raise ValueError("Longitude must be between -180 and < 180.")
|
|
return lat, lon
|
|
except ValueError as e:
|
|
raise ValueError(f"Invalid input: {e}") from e
|
|
|
|
def calculate_single_elevation(self) -> None:
|
|
"""Handles 'Get Elevation' button click. Also displays browse image."""
|
|
self._clear_canvas() # Pulisci canvas precedente
|
|
self.result_label.config(text="Result: Processing...")
|
|
self.get_elevation_button.config(state=tk.DISABLED)
|
|
self.root.update_idletasks()
|
|
|
|
try:
|
|
latitude, longitude = self._validate_coordinates(
|
|
self.lat_entry.get(), self.lon_entry.get()
|
|
)
|
|
logging.info(f"GUI: Requesting elevation for Lat: {latitude}, Lon: {longitude}")
|
|
elevation_value = self.elevation_manager.get_elevation(latitude, longitude)
|
|
logging.info(f"GUI: Elevation manager returned: {elevation_value}")
|
|
|
|
if elevation_value is None:
|
|
result_text = "Result: Could not retrieve elevation (Tile unavailable or processing error)."
|
|
# Non mostrare immagine se i dati non ci sono
|
|
elif math.isnan(elevation_value):
|
|
result_text = "Result: Point is on a nodata area within the tile."
|
|
# Mostra comunque l'immagine browse se esiste
|
|
self.display_single_browse_image(latitude, longitude)
|
|
else:
|
|
result_text = f"Result: Elevation is {elevation_value:.2f} meters"
|
|
# Mostra l'immagine browse associata
|
|
self.display_single_browse_image(latitude, longitude)
|
|
|
|
self.result_label.config(text=result_text)
|
|
|
|
except ValueError as ve:
|
|
logging.warning(f"Input validation error: {ve}")
|
|
messagebox.showerror("Input Error", str(ve), parent=self.root)
|
|
self.result_label.config(text="Result: Invalid input.")
|
|
except Exception as e:
|
|
logging.exception("An unexpected error occurred during single point elevation retrieval.")
|
|
messagebox.showerror("Error", f"An unexpected error occurred: {e}", parent=self.root)
|
|
self.result_label.config(text="Result: Error during processing.")
|
|
finally:
|
|
self.get_elevation_button.config(state=tk.NORMAL)
|
|
|
|
|
|
def display_single_browse_image(self, lat: float, lon: float):
|
|
"""Carica e visualizza l'immagine browse per un singolo tile."""
|
|
self._clear_canvas()
|
|
logging.info(f"Attempting to display browse image for ({lat},{lon})")
|
|
browse_path = self.elevation_manager.get_browse_image_path(lat, lon)
|
|
|
|
if browse_path and os.path.exists(browse_path):
|
|
try:
|
|
# Apri immagine con Pillow
|
|
img = Image.open(browse_path).convert("RGBA") # Converti in RGBA per disegno alpha?
|
|
|
|
# Disegna info sul tile sull'immagine
|
|
draw = ImageDraw.Draw(img)
|
|
lat_coord, lon_coord = self.elevation_manager._get_tile_base_coordinates(lat, lon)
|
|
tile_name = self.elevation_manager._get_nasadem_tile_base_name(lat_coord, lon_coord)
|
|
text = f"{tile_name}\nSource: NASADEM HGT"
|
|
# Calcola posizione testo (basso a destra)
|
|
text_bbox = draw.textbbox((0, 0), text, font=self.label_font)
|
|
text_width = text_bbox[2] - text_bbox[0]
|
|
text_height = text_bbox[3] - text_bbox[1]
|
|
text_x = img.width - text_width - 10 # Margine 10px
|
|
text_y = img.height - text_height - 10
|
|
|
|
# Disegna rettangolo di sfondo (opzionale)
|
|
# draw.rectangle([text_x-2, text_y-2, text_x+text_width+2, text_y+text_height+2], fill=(0,0,0,128)) # Sfondo nero semi-trasp.
|
|
draw.rectangle([text_x-2, text_y-2, text_x+text_width+2, text_y+text_height+2], fill=TILE_TEXT_BG_COLOR) # Sfondo nero opaco
|
|
draw.text((text_x, text_y), text, fill=TILE_TEXT_COLOR, font=self.label_font)
|
|
|
|
# Ridimensiona immagine per adattarla al canvas mantenendo l'aspect ratio
|
|
img.thumbnail((self.image_canvas.winfo_width(), self.image_canvas.winfo_height()))
|
|
|
|
# Converti per Tkinter e visualizza
|
|
self.current_photo_image = ImageTk.PhotoImage(img)
|
|
self.image_canvas.create_image(0, 0, anchor=tk.NW, image=self.current_photo_image)
|
|
logging.info(f"Displayed browse image: {browse_path}")
|
|
|
|
except Exception as e:
|
|
logging.error(f"Error processing or displaying browse image {browse_path}: {e}", exc_info=True)
|
|
self.image_canvas.create_text(10, 10, anchor=tk.NW, text="Error loading image.", fill="red")
|
|
else:
|
|
logging.warning(f"Browse image not found locally for tile containing ({lat},{lon}).")
|
|
self.image_canvas.create_text(10, 10, anchor=tk.NW, text="Browse image not available.", fill="orange")
|
|
|
|
|
|
def _validate_area_bounds(self) -> Tuple[float, float, float, float]:
|
|
"""Helper to validate area download bounds."""
|
|
try:
|
|
min_lat = float(self.min_lat_entry.get())
|
|
max_lat = float(self.max_lat_entry.get())
|
|
min_lon = float(self.min_lon_entry.get())
|
|
max_lon = float(self.max_lon_entry.get())
|
|
if not (-90 <= min_lat < 90 and -90 <= max_lat < 90 and
|
|
-180 <= min_lon < 180 and -180 <= max_lon < 180):
|
|
raise ValueError("Coordinates out of valid range.")
|
|
if min_lat >= max_lat: raise ValueError("Min latitude must be less than Max latitude.")
|
|
if min_lon >= max_lon: raise ValueError("Min longitude must be less than Max longitude.")
|
|
return min_lat, min_lon, max_lat, max_lon
|
|
except ValueError as e:
|
|
raise ValueError(f"Invalid area input: {e}") from e
|
|
|
|
|
|
def start_area_download_thread(self):
|
|
"""Starts the area download process in a separate thread."""
|
|
self._clear_canvas() # Pulisci immagine precedente
|
|
try:
|
|
min_lat, min_lon, max_lat, max_lon = self._validate_area_bounds()
|
|
# Memorizza le coordinate per usarle dopo il download per visualizzare l'area
|
|
self.last_area_coords = (min_lat, min_lon, max_lat, max_lon)
|
|
|
|
self.download_area_button.config(state=tk.DISABLED)
|
|
self.download_status_label.config(text="Status: Starting download...")
|
|
self.root.update_idletasks()
|
|
|
|
download_thread = threading.Thread(
|
|
target=self._perform_area_download,
|
|
args=(min_lat, min_lon, max_lat, max_lon),
|
|
daemon=True
|
|
)
|
|
download_thread.start()
|
|
|
|
except ValueError as ve:
|
|
logging.warning(f"Area input validation error: {ve}")
|
|
messagebox.showerror("Input Error", str(ve), parent=self.root)
|
|
self.download_status_label.config(text="Status: Invalid input.")
|
|
self.last_area_coords = None # Resetta coords se input errato
|
|
except Exception as e:
|
|
logging.exception("Error initiating area download.")
|
|
messagebox.showerror("Error", f"Could not start download process: {e}", parent=self.root)
|
|
self.download_status_label.config(text="Status: Error starting download.")
|
|
self.download_area_button.config(state=tk.NORMAL)
|
|
self.last_area_coords = None
|
|
|
|
|
|
def _perform_area_download(self, min_lat, min_lon, max_lat, max_lon):
|
|
"""Download logic executed in the background thread."""
|
|
final_status = "Status: Unknown error."
|
|
success = False
|
|
processed = 0
|
|
obtained = 0
|
|
try:
|
|
logging.info(f"Thread: Calling download_area for Lat [{min_lat}, {max_lat}], Lon [{min_lon}, {max_lon}]")
|
|
self.root.after(0, lambda: self.download_status_label.config(
|
|
text="Status: Downloading/Extracting tiles... (See console log)"
|
|
))
|
|
|
|
processed, obtained = self.elevation_manager.download_area(min_lat, min_lon, max_lat, max_lon)
|
|
|
|
logging.info(f"Thread: Download complete. Processed: {processed}, Obtained HGT: {obtained}")
|
|
final_status = f"Status: Download complete. Processed {processed}, Obtained HGT {obtained}."
|
|
success = True # Assume successo se non ci sono eccezioni
|
|
|
|
except Exception as e:
|
|
logging.exception("Error during background area download execution.")
|
|
final_status = f"Status: Error during download: {type(e).__name__}"
|
|
success = False
|
|
|
|
# Aggiorna UI e visualizza immagine area (se successo)
|
|
self.root.after(0, self._update_download_ui_after_completion, final_status, success, processed, obtained)
|
|
|
|
|
|
def _update_download_ui_after_completion(self, status_message, download_success, processed, obtained):
|
|
"""Updates GUI after download thread finishes and triggers area image display."""
|
|
self.download_status_label.config(text=status_message)
|
|
self.download_area_button.config(state=tk.NORMAL)
|
|
|
|
# Mostra messaggio riassuntivo
|
|
if not download_success:
|
|
brief_error = status_message.split(":")[-1].strip()
|
|
messagebox.showerror("Download Error", f"Download process finished with error: {brief_error}\nCheck logs.", parent=self.root)
|
|
else:
|
|
summary = f"Processed {processed} tiles.\nObtained {obtained} HGT tiles."
|
|
messagebox.showinfo("Download Complete", summary, parent=self.root)
|
|
# Se il download è andato bene e avevamo coordinate valide, mostra l'immagine dell'area
|
|
if self.last_area_coords:
|
|
self.display_area_browse_images(*self.last_area_coords)
|
|
|
|
# Resetta le coordinate area dopo l'uso
|
|
# self.last_area_coords = None # Forse non resettare? Utile per vedere cosa si è chiesto
|
|
|
|
|
|
def display_area_browse_images(self, min_lat, min_lon, max_lat, max_lon):
|
|
"""Carica, cuce e visualizza le immagini browse per l'area scaricata."""
|
|
self._clear_canvas()
|
|
logging.info(f"Attempting to display browse images for area [{min_lat},{min_lon},{max_lat},{max_lon}]")
|
|
|
|
try:
|
|
# Calcola la griglia di tile necessaria
|
|
start_lat = math.floor(min_lat)
|
|
end_lat = math.floor(max_lat)
|
|
start_lon = math.floor(min_lon)
|
|
end_lon = math.floor(max_lon)
|
|
|
|
# Determina dimensioni griglia
|
|
num_lat_tiles = end_lat - start_lat + 1
|
|
num_lon_tiles = end_lon - start_lon + 1
|
|
|
|
if num_lat_tiles <= 0 or num_lon_tiles <= 0:
|
|
logging.warning("No tiles in the specified area range.")
|
|
self.image_canvas.create_text(10, 10, anchor=tk.NW, text="No tiles in selected area.", fill="orange")
|
|
return
|
|
|
|
# Carica le immagini (o crea placeholder se mancano)
|
|
tile_images = {}
|
|
max_tile_w, max_tile_h = 0, 0 # Per determinare dimensione cella griglia
|
|
|
|
for r, lat_coord in enumerate(range(end_lat, start_lat - 1, -1)): # Itera da Nord a Sud per visualizzazione mappa
|
|
for c, lon_coord in enumerate(range(start_lon, end_lon + 1)): # Itera da Ovest a Est
|
|
img_path = self.elevation_manager._get_local_browse_path(lat_coord, lon_coord) # Usa metodo interno per path
|
|
img = None
|
|
tile_key = (lat_coord, lon_coord)
|
|
|
|
if img_path and os.path.exists(img_path):
|
|
try:
|
|
# Usa cache per non ricaricare da disco se già fatto
|
|
if tile_key in self.tile_images_cache:
|
|
img = self.tile_images_cache[tile_key]
|
|
else:
|
|
img = Image.open(img_path).convert("RGB") # Converti in RGB per coerenza
|
|
self.tile_images_cache[tile_key] = img
|
|
max_tile_w = max(max_tile_w, img.width)
|
|
max_tile_h = max(max_tile_h, img.height)
|
|
except Exception as e:
|
|
logging.warning(f"Could not load browse image {img_path}: {e}")
|
|
else:
|
|
logging.debug(f"Browse image not found for tile ({lat_coord},{lon_coord}), using placeholder.")
|
|
|
|
# Memorizza immagine o None se non trovata/caricata
|
|
tile_images[tile_key] = img
|
|
|
|
if max_tile_w == 0 or max_tile_h == 0:
|
|
# Prova a stimare una dimensione se nessuna immagine è stata caricata
|
|
# Potremmo basarci sulla dimensione attesa (es. SRTM browse sono spesso ~1200x1200?)
|
|
# O più semplicemente, usa una dimensione fissa per il placeholder
|
|
max_tile_w, max_tile_h = 100, 100 # Dimensione placeholder
|
|
logging.warning("No browse images loaded, using default placeholder size.")
|
|
|
|
|
|
# Crea immagine composita
|
|
total_width = num_lon_tiles * max_tile_w
|
|
total_height = num_lat_tiles * max_tile_h
|
|
composite_img = Image.new('RGB', (total_width, total_height), PLACEHOLDER_COLOR) # Sfondo grigio
|
|
draw = ImageDraw.Draw(composite_img)
|
|
|
|
# Incolla i tile e disegna griglia/testo
|
|
for r, lat_coord in enumerate(range(end_lat, start_lat - 1, -1)): # Da Nord a Sud
|
|
for c, lon_coord in enumerate(range(start_lon, end_lon + 1)): # Da Ovest a Est
|
|
tile_key = (lat_coord, lon_coord)
|
|
img = tile_images.get(tile_key)
|
|
|
|
# Calcola posizione top-left del tile nella griglia composita
|
|
paste_x = c * max_tile_w
|
|
paste_y = r * max_tile_h
|
|
|
|
if img:
|
|
# Ridimensiona se necessario per adattarsi alla cella (se le img hanno dim diverse)
|
|
if img.width != max_tile_w or img.height != max_tile_h:
|
|
img = img.resize((max_tile_w, max_tile_h), Image.Resampling.LANCZOS) # Usa resampling di qualità
|
|
composite_img.paste(img, (paste_x, paste_y))
|
|
|
|
# Disegna bordo rosso
|
|
draw.rectangle(
|
|
[paste_x, paste_y, paste_x + max_tile_w -1, paste_y + max_tile_h -1],
|
|
outline=TILE_BORDER_COLOR, width=1
|
|
)
|
|
|
|
# Disegna nome tile in basso a destra della cella
|
|
tile_name = self.elevation_manager._get_nasadem_tile_base_name(lat_coord, lon_coord)
|
|
text = f"{tile_name}"
|
|
text_bbox = draw.textbbox((0, 0), text, font=self.label_font)
|
|
text_w = text_bbox[2] - text_bbox[0]
|
|
text_h = text_bbox[3] - text_bbox[1]
|
|
text_pos_x = paste_x + max_tile_w - text_w - 5 # Margine 5px
|
|
text_pos_y = paste_y + max_tile_h - text_h - 5
|
|
# Rettangolo sfondo testo
|
|
draw.rectangle([text_pos_x-1, text_pos_y-1, text_pos_x+text_w+1, text_pos_y+text_h+1], fill=TILE_TEXT_BG_COLOR)
|
|
draw.text((text_pos_x, text_pos_y), text, fill=TILE_TEXT_COLOR, font=self.label_font)
|
|
|
|
# Ridimensiona l'immagine composita per adattarla al canvas
|
|
composite_img.thumbnail((self.image_canvas.winfo_width(), self.image_canvas.winfo_height()), Image.Resampling.LANCZOS)
|
|
|
|
# Converti per Tkinter e visualizza
|
|
self.current_photo_image = ImageTk.PhotoImage(composite_img)
|
|
self.image_canvas.create_image(0, 0, anchor=tk.NW, image=self.current_photo_image)
|
|
logging.info(f"Displayed composite browse image for area.")
|
|
|
|
except Exception as e:
|
|
logging.error(f"Error creating or displaying composite browse image: {e}", exc_info=True)
|
|
self.image_canvas.create_text(10, 10, anchor=tk.NW, text="Error displaying area image.", fill="red")
|
|
|
|
|
|
# --- Main Execution Block (invariato) ---
|
|
if __name__ == "__main__":
|
|
try:
|
|
manager = ElevationManager(tile_directory=DEFAULT_TILE_DIR)
|
|
except Exception as e:
|
|
# ... (gestione errore init manager come prima) ...
|
|
logging.critical(f"Failed to initialize ElevationManager: {e}", exc_info=True)
|
|
try:
|
|
temp_root = tk.Tk(); temp_root.withdraw()
|
|
messagebox.showerror("Initialization Error", f"Failed to initialize Elevation Manager: {e}\nCheck config/permissions.", parent=None)
|
|
temp_root.destroy()
|
|
except tk.TclError: print(f"CRITICAL: Failed to initialize Elevation Manager: {e}")
|
|
exit(1)
|
|
|
|
try:
|
|
root_window = tk.Tk()
|
|
app = ElevationApp(root_window, manager)
|
|
root_window.mainloop()
|
|
except Exception as e:
|
|
# ... (gestione errore app come prima) ...
|
|
logging.critical(f"An error occurred running the application: {e}", exc_info=True)
|
|
try:
|
|
temp_root = tk.Tk(); temp_root.withdraw()
|
|
messagebox.showerror("Application Error", f"A critical error occurred: {e}\nCheck logs.", parent=None)
|
|
temp_root.destroy()
|
|
except tk.TclError: print(f"CRITICAL: Application failed unexpectedly: {e}")
|
|
exit(1) |