diff --git a/README.md b/README.md index 152a4e2..0e9e052 100644 --- a/README.md +++ b/README.md @@ -4,30 +4,31 @@ **IT** | [EN](#english-presentation) -Questo strumento fornisce un'interfaccia grafica (GUI) per interagire con i dati di elevazione digitale del terreno (DEM), principalmente utilizzando i dataset NASADEM HGT. Permette agli utenti di: +GeoElevation Tool è un'applicazione versatile e una libreria Python progettata per interagire con i dati di elevazione digitale del terreno (DEM), principalmente utilizzando i dataset NASADEM HGT. Offre un'interfaccia grafica (GUI) per un facile utilizzo interattivo, un'interfaccia a riga di comando (CLI) per interrogazioni rapide e un'API di libreria per l'integrazione in altri progetti Python. -* **Ottenere l'Elevazione Puntuale:** Inserire coordinate geografiche (latitudine e longitudine) per recuperare l'elevazione di un punto specifico. -* **Visualizzare Dati DEM:** - * Mostrare un'immagine di anteprima (browse image) del tile DEM corrispondente al punto selezionato. - * Generare e visualizzare una rappresentazione 3D interattiva del tile DEM, con opzioni per lo smoothing e l'interpolazione per una resa più realistica del terreno. -* **Scaricare Aree DEM:** Definire un'area geografica (bounding box) per scaricare automaticamente tutti i tile NASADEM HGT necessari per coprire tale area. I file vengono memorizzati in una cache locale per un accesso futuro più rapido. -* **Visualizzare Aree Scaricate:** Creare e mostrare un'immagine composita delle anteprime (browse images) di tutti i tile scaricati per un'area definita. +Permette agli utenti di: -Lo strumento gestisce l'autenticazione con NASA Earthdata Login (tramite file `.netrc`), il download dei dati, l'estrazione e la conversione necessarie per la visualizzazione. +* **Ottenere l'Elevazione Puntuale:** Sia tramite GUI, CLI o API, inserendo coordinate geografiche (latitudine e longitudine) per recuperare l'elevazione di un punto specifico. +* **Visualizzare Dati DEM (via GUI):** + * Mostrare un'immagine di anteprima del tile DEM. + * Generare e visualizzare una rappresentazione 3D interattiva del tile DEM, con opzioni per lo smoothing e l'interpolazione. +* **Scaricare Aree DEM (via GUI):** Definire un'area geografica per scaricare automaticamente tutti i tile NASADEM HGT necessari. +* **Visualizzare Aree Scaricate (via GUI):** Creare e mostrare un'immagine composita delle anteprime per un'area definita. + +Lo strumento gestisce l'autenticazione con NASA Earthdata Login (tramite file `.netrc`), il download dei dati, l'estrazione e la conversione. **Per informazioni dettagliate sull'utilizzo come libreria o da riga di comando, consultare la sezione "Utilizzo Avanzato" di seguito.** ### Caratteristiche Principali -* Interfaccia utente intuitiva basata su Tkinter. +* Interfaccia utente grafica (GUI) intuitiva basata su Tkinter. +* Interfaccia a riga di comando (CLI) per il recupero rapido dell'elevazione. +* API Python per l'integrazione come libreria in altre applicazioni. * Recupero dell'elevazione per coordinate specifiche. -* Download automatico e caching dei tile NASADEM (file HGT e immagini di anteprima). -* Visualizzazione 2D delle immagini di anteprima dei tile. -* Visualizzazione 3D interattiva dei dati di elevazione con: - * Smoothing Gaussiano opzionale per ridurre il rumore. - * Interpolazione Spline per una superficie del terreno più liscia e realistica. - * Controllo della densità della griglia di rendering per bilanciare dettaglio e prestazioni. -* Download di tile DEM per aree geografiche definite. -* Creazione di immagini composite per le aree scaricate. -* Utilizzo di multiprocessing per operazioni di visualizzazione lunghe, mantenendo la GUI reattiva. +* Download automatico e caching dei tile NASADEM. +* Visualizzazione 2D delle immagini di anteprima (GUI). +* Visualizzazione 3D interattiva dei dati di elevazione con opzioni avanzate (GUI). +* Download di tile DEM per aree geografiche (GUI). +* Creazione di immagini composite per aree (GUI). +* Feedback visivo opzionale (finestra di progresso) durante le operazioni di recupero dati quando usato come libreria. ### Requisiti @@ -42,39 +43,132 @@ Lo strumento gestisce l'autenticazione con NASA Earthdata Login (tramite file `. È necessario configurare un file `.netrc` nella propria directory home con le credenziali per `urs.earthdata.nasa.gov` per scaricare i dati DEM protetti. +### Installazione +*(Aggiungere se necessario, es. `pip install geoelevation` se pubblicato, o istruzioni per `requirements.txt`)* + +### Utilizzo + +#### Avvio dell'Interfaccia Grafica (GUI) + +Per avviare l'interfaccia grafica, eseguire il modulo dalla directory principale del progetto (la cartella che contiene `geoelevation/`): + +```bash +python -m geoelevation +``` +Oppure, se è stato installato come pacchetto: +```bash +geoelevation_gui # (o il nome dell'entry point script se definito in setup.py) +``` + +#### Utilizzo da Riga di Comando (CLI) + +È possibile ottenere l'elevazione per un punto specifico direttamente dalla riga di comando: + +```bash +python -m geoelevation --lat --lon [--show-progress] [--verbose] +``` + +Argomenti: +* `--lat LATITUDINE`: Latitudine del punto (es. `45.0`). +* `--lon LONGITUDINE`: Longitudine del punto (es. `7.0`). +* `--show-progress`: (Opzionale) Mostra una semplice finestra di dialogo di avanzamento durante il recupero dei dati. +* `--verbose` o `-v`: (Opzionale) Abilita output di logging più dettagliato. +* `--help`: Mostra il messaggio di aiuto con tutti gli argomenti. + +Esempio: +```bash +python -m geoelevation --lat 40.7128 --lon -74.0060 --show-progress +``` +Output atteso: +``` +Elevation: meters +``` +oppure `Elevation: NODATA` o `Elevation: UNAVAILABLE`. + +#### Utilizzo come Libreria Python + +Il package `geoelevation` può essere importato nei tuoi script Python per recuperare programmaticamente i dati di elevazione. + +La funzione principale esposta è `get_point_elevation`: + +```python +from geoelevation import get_point_elevation +import logging + +# Opzionale: configura il logging per vedere i messaggi da geoelevation +# logging.basicConfig(level=logging.INFO) + +latitude = 45.0 +longitude = 7.0 + +try: + # Recupera l'elevazione, mostrando opzionalmente una finestra di progresso + elevation = get_point_elevation( + latitude, + longitude, + show_progress_dialog=True, + progress_dialog_message="Sto cercando l'elevazione per te..." # Messaggio personalizzato + ) + + if elevation is None: + print(f"Impossibile recuperare l'elevazione per ({latitude}, {longitude}).") + elif elevation != elevation: # Controllo per NaN (Not a Number) + print(f"Il punto ({latitude}, {longitude}) si trova su un'area NoData.") + else: + print(f"L'elevazione a ({latitude}, {longitude}) è: {elevation:.2f} metri.") + +except RuntimeError as e: + print(f"Errore durante l'inizializzazione della libreria GeoElevation: {e}") +except Exception as e: + print(f"Si è verificato un errore imprevisto: {e}") + +``` + +**Dettagli della funzione `get_point_elevation`:** + +* `get_point_elevation(latitude: float, longitude: float, show_progress_dialog: bool = False, progress_dialog_message: str = "Messaggio di default...") -> Optional[float]` + * `latitude`, `longitude`: Coordinate geografiche del punto. + * `show_progress_dialog`: Se `True`, mostra una finestra di dialogo non bloccante durante il recupero dei dati. Utile se la funzione viene chiamata da un'applicazione con una propria GUI che potrebbe altrimenti sembrare bloccata. + * `progress_dialog_message`: Permette di personalizzare il messaggio visualizzato nella finestra di dialogo di progresso. + * **Restituisce**: L'elevazione in metri come `float`, `float('nan')` se il punto è un'area NoData, o `None` se l'elevazione non può essere determinata o si verifica un errore. + * **Solleva**: `RuntimeError` se le dipendenze critiche (es. Rasterio) non sono disponibili o se `ElevationManager` non può essere inizializzato. + +*(Aggiungere altre sezioni come Struttura Progetto, Screenshot, Limitazioni, Licenza, Contribuire come suggerito prima)* + --- --- ## GeoElevation Tool -**(Presentazione in Italiano Sopra)** +**(Italian Presentation Above)** **EN** | [IT](#geoelevation-tool-1) -This tool provides a Graphical User Interface (GUI) for interacting with digital elevation model (DEM) data, primarily utilizing the NASADEM HGT datasets. It allows users to: +GeoElevation Tool is a versatile Python application and library designed for interacting with digital elevation model (DEM) data, primarily utilizing the NASADEM HGT datasets. It offers a Graphical User Interface (GUI) for easy interactive use, a Command Line Interface (CLI) for quick queries, and a library API for integration into other Python projects. -* **Get Point Elevation:** Input geographic coordinates (latitude and longitude) to retrieve the elevation of a specific point. -* **Visualize DEM Data:** - * Display a browse image (preview) of the DEM tile corresponding to the selected point. - * Generate and display an interactive 3D representation of the DEM tile, with options for smoothing and interpolation for a more realistic terrain rendering. -* **Download DEM Areas:** Define a geographical area (bounding box) to automatically download all necessary NASADEM HGT tiles covering that area. Files are stored in a local cache for faster future access. -* **Visualize Downloaded Areas:** Create and show a composite image of the browse images for all downloaded tiles within a defined area. +It allows users to: -The tool handles authentication with NASA Earthdata Login (via a `.netrc` file), data downloading, extraction, and the necessary conversions for visualization. +* **Get Point Elevation:** Via GUI, CLI, or API, by inputting geographic coordinates (latitude and longitude) to retrieve the elevation of a specific point. +* **Visualize DEM Data (via GUI):** + * Display a browse image (preview) of the DEM tile. + * Generate and display an interactive 3D representation of the DEM tile, with options for smoothing and interpolation. +* **Download DEM Areas (via GUI):** Define a geographical area to automatically download all necessary NASADEM HGT tiles. +* **Visualize Downloaded Areas (via GUI):** Create and show a composite browse image for a defined area. + +The tool handles authentication with NASA Earthdata Login (via a `.netrc` file), data downloading, extraction, and conversion. **For detailed information on using it as a library or from the command line, please refer to the "Advanced Usage" section below.** ### Key Features -* Intuitive Tkinter-based user interface. +* Intuitive Tkinter-based Graphical User Interface (GUI). +* Command Line Interface (CLI) for quick elevation retrieval. +* Python API for integration as a library into other applications. * Elevation retrieval for specific coordinates. -* Automatic download and caching of NASADEM tiles (HGT files and browse images). -* 2D visualization of tile browse images. -* Interactive 3D visualization of elevation data featuring: - * Optional Gaussian smoothing to reduce noise. - * Spline interpolation for a smoother, more realistic terrain surface. - * Control over the rendering grid density to balance detail and performance. -* DEM tile download for defined geographical areas. -* Composite image creation for downloaded areas. -* Utilizes multiprocessing for lengthy visualization tasks, keeping the GUI responsive. +* Automatic download and caching of NASADEM tiles. +* 2D visualization of tile browse images (GUI). +* Interactive 3D visualization of elevation data with advanced options (GUI). +* DEM tile download for defined geographical areas (GUI). +* Composite image creation for areas (GUI). +* Optional visual feedback (progress window) during data retrieval operations when used as a library. ### Requirements @@ -88,3 +182,95 @@ The tool handles authentication with NASA Earthdata Login (via a `.netrc` file), * SciPy (for smoothing and interpolation in 3D visualization) A `.netrc` file in your home directory must be configured with credentials for `urs.earthdata.nasa.gov` to download protected DEM data. + +### Installation +*(Add if needed, e.g., `pip install geoelevation` if published, or `requirements.txt` instructions)* + +### Usage + +#### Launching the Graphical User Interface (GUI) + +To start the GUI, run the module from the project's root directory (the folder containing the `geoelevation/` directory): + +```bash +python -m geoelevation +``` +Alternatively, if installed as a package: +```bash +geoelevation_gui # (or the entry point script name if defined in setup.py) +``` + +#### Command Line Interface (CLI) Usage + +You can get the elevation for a specific point directly from the command line: + +```bash +python -m geoelevation --lat --lon [--show-progress] [--verbose] +``` + +Arguments: +* `--lat LATITUDE`: Latitude of the point (e.g., `45.0`). +* `--lon LONGITUDE`: Longitude of the point (e.g., `7.0`). +* `--show-progress`: (Optional) Displays a simple progress dialog window during data retrieval. +* `--verbose` or `-v`: (Optional) Enables more detailed logging output. +* `--help`: Shows the help message with all arguments. + +Example: +```bash +python -m geoelevation --lat 40.7128 --lon -74.0060 --show-progress +``` +Expected output: +``` +Elevation: meters +``` +or `Elevation: NODATA` or `Elevation: UNAVAILABLE`. + +#### Using as a Python Library + +The `geoelevation` package can be imported into your Python scripts to programmatically retrieve elevation data. + +The primary function exposed is `get_point_elevation`: + +```python +from geoelevation import get_point_elevation +import logging + +# Optional: configure logging to see messages from geoelevation +# logging.basicConfig(level=logging.INFO) + +latitude = 45.0 +longitude = 7.0 + +try: + # Retrieve elevation, optionally showing a progress window + elevation = get_point_elevation( + latitude, + longitude, + show_progress_dialog=True, + progress_dialog_message="Fetching elevation data for you..." # Custom message + ) + + if elevation is None: + print(f"Could not retrieve elevation for ({latitude}, {longitude}).") + elif elevation != elevation: # Check for NaN (Not a Number) + print(f"The point ({latitude}, {longitude}) is on a NoData area.") + else: + print(f"The elevation at ({latitude}, {longitude}) is: {elevation:.2f} meters.") + +except RuntimeError as e: + print(f"Error initializing GeoElevation library: {e}") +except Exception as e: + print(f"An unexpected error occurred: {e}") + +``` + +**`get_point_elevation` function details:** + +* `get_point_elevation(latitude: float, longitude: float, show_progress_dialog: bool = False, progress_dialog_message: str = "Default progress message...") -> Optional[float]` + * `latitude`, `longitude`: Geographic coordinates of the point. + * `show_progress_dialog`: If `True`, displays a non-blocking dialog window during data retrieval. Useful if the function is called from an application with its own GUI that might otherwise appear to freeze. + * `progress_dialog_message`: Allows customization of the message displayed in the progress dialog. + * **Returns**: The elevation in meters as a `float`, `float('nan')` if the point is a NoData area, or `None` if the elevation cannot be determined or an error occurs. + * **Raises**: `RuntimeError` if critical dependencies (e.g., Rasterio) are unavailable or if the `ElevationManager` cannot be initialized. + +*(Add other sections like Project Structure, Screenshots, Limitations, License, Contributing as suggested before)* diff --git a/geoelevation/__init__.py b/geoelevation/__init__.py index e69de29..a8a6440 100644 --- a/geoelevation/__init__.py +++ b/geoelevation/__init__.py @@ -0,0 +1,214 @@ +# geoelevation/__init__.py +""" +GeoElevation Package. +Provides functionalities to retrieve elevation data for geographic coordinates +and a GUI for interactive use. +""" + +import os +import tkinter as tk +from tkinter import ttk +import threading +import logging +from typing import Optional, Tuple +import queue # For thread-safe communication + +# Import necessary components from within the package +from .elevation_manager import ElevationManager, RASTERIO_AVAILABLE + +# Try to import config, if not, define default +try: + from .config import DEFAULT_CACHE_DIR +except ImportError: + DEFAULT_CACHE_DIR = "elevation_cache" + logging.info("GeoElevation: config.py not found, using default cache directory.") + + +_library_elevation_manager: Optional[ElevationManager] = None +_manager_lock = threading.Lock() + +def _get_library_manager() -> ElevationManager: + global _library_elevation_manager + if not RASTERIO_AVAILABLE: + raise RuntimeError("Rasterio library is not installed, required for elevation retrieval.") + + with _manager_lock: + if _library_elevation_manager is None: + try: + cache_dir = os.environ.get("GEOELEVATION_CACHE_DIR", DEFAULT_CACHE_DIR) + _library_elevation_manager = ElevationManager(tile_directory=cache_dir) + logging.info(f"GeoElevation library: Initialized ElevationManager (cache: {cache_dir})") + except Exception as e: + logging.error(f"GeoElevation library: Failed to init ElevationManager: {e}", exc_info=True) + raise RuntimeError(f"Failed to initialize ElevationManager: {e}") from e + return _library_elevation_manager + +class _ProgressWindow(threading.Thread): + """ + A simple non-blocking progress window using Tkinter, designed to be + controlled from another thread. + """ + def __init__(self, title: str = "GeoElevation", message: str = "Processing... Please wait."): + super().__init__(daemon=True) + self.title = title + self.message = message + self.root: Optional[tk.Tk] = None + self.ready_event = threading.Event() # Signals when Tkinter root is ready + self._stop_event = threading.Event() # Signals the Tkinter loop to stop + + def run(self): + """Runs the Tkinter event loop for the progress window in this thread.""" + try: + self.root = tk.Tk() + self.root.title(self.title) + window_width = 350 + window_height = 100 + screen_width = self.root.winfo_screenwidth() + screen_height = self.root.winfo_screenheight() + center_x = int(screen_width/2 - window_width/2) + center_y = int(screen_height/2 - window_height/2) + self.root.geometry(f'{window_width}x{window_height}+{center_x}+{center_y}') + self.root.resizable(False, False) + # self.root.attributes('-topmost', True) # Optional: keep window on top + + main_frame = ttk.Frame(self.root, padding="10") + main_frame.pack(expand=True, fill=tk.BOTH) + ttk.Label(main_frame, text=self.message, font=("Helvetica", 10)).pack(pady=10) + progress_bar = ttk.Progressbar(main_frame, mode='indeterminate', length=300) + progress_bar.pack(pady=10) + progress_bar.start(20) # Animation speed + + # Override close button ('X') to signal stop + self.root.protocol("WM_DELETE_WINDOW", self._signal_stop_from_tk) + + self.ready_event.set() # Signal that the root window is created and configured + + # Periodically check the stop event + self._check_stop_periodic() + self.root.mainloop() + + except Exception as e: + logging.warning(f"GeoElevation ProgressWindow: Error during run: {e}", exc_info=True) + self.ready_event.set() # Set event even on error to unblock caller + finally: + # Ensure cleanup happens if mainloop exits for any reason + if self.root: + try: + # self.root.destroy() # This should be handled by mainloop exit + pass + except tk.TclError as e: # Catch Tcl errors during destroy if already destroyed + if "application has been destroyed" not in str(e).lower(): + logging.warning(f"GeoElevation ProgressWindow: TclError on destroy: {e}") + except Exception as e_destroy: + logging.warning(f"GeoElevation ProgressWindow: Exception on final destroy: {e_destroy}") + + logging.debug("GeoElevation ProgressWindow: Thread finished.") + + def _check_stop_periodic(self): + """Periodically checks if the stop event has been set.""" + if self._stop_event.is_set(): + if self.root: + try: + # This is called from the Tkinter thread, so it's safe + self.root.quit() # Exit mainloop cleanly + # self.root.destroy() # Let mainloop exit handle destroy + except Exception as e: + logging.warning(f"GeoElevation ProgressWindow: Error during quit in _check_stop_periodic: {e}") + else: + if self.root: # Check if root still exists + try: + self.root.after(100, self._check_stop_periodic) # Check again in 100ms + except tk.TclError: # Happens if root is destroyed externally + pass + + + def _signal_stop_from_tk(self): + """Called when the window's 'X' button is pressed (from Tk thread).""" + logging.debug("GeoElevation ProgressWindow: WM_DELETE_WINDOW invoked.") + self._stop_event.set() # Signal the stop + # The _check_stop_periodic method will then call root.quit() + + def request_stop(self): + """Requests the progress window to stop (can be called from any thread).""" + logging.debug("GeoElevation ProgressWindow: Stop requested from external thread.") + self._stop_event.set() + # The _check_stop_periodic method running in the Tk thread will handle the actual quit. + + +def get_point_elevation( + latitude: float, + longitude: float, + show_progress_dialog: bool = False, + progress_dialog_message: str = "Retrieving elevation data...\nThis may take a moment." +) -> Optional[float]: + """ + Retrieves the elevation for a given geographic point. + Can optionally display a non-blocking progress dialog. + """ + progress_win_thread: Optional[_ProgressWindow] = None + try: + manager = _get_library_manager() + + if not (-90 <= latitude < 90 and -180 <= longitude < 180): + logging.error(f"GeoElevation: Invalid coordinates: Lat {latitude}, Lon {longitude}") + return None + + if show_progress_dialog: + logging.debug("GeoElevation: Preparing progress dialog.") + progress_win_thread = _ProgressWindow(message=progress_dialog_message) + progress_win_thread.start() + # Wait for the Tkinter window to be ready before proceeding + # This prevents the main operation from finishing before the window even appears + progress_win_thread.ready_event.wait(timeout=2.0) # Timeout of 2s + if not progress_win_thread.ready_event.is_set(): + logging.warning("GeoElevation: Progress dialog did not become ready in time.") + # Decide whether to proceed without dialog or stop + # For now, proceed, but log it. + + logging.info(f"GeoElevation: Requesting elevation for Lat: {latitude}, Lon: {longitude}") + elevation = manager.get_elevation(latitude, longitude) + + if elevation is None: + logging.warning(f"GeoElevation: Elevation unavailable for Lat: {latitude}, Lon: {longitude}") + elif isinstance(elevation, float) and elevation != elevation: # Check for NaN + logging.info(f"GeoElevation: Point Lat: {latitude}, Lon: {longitude} is NoData.") + else: + logging.info(f"GeoElevation: Elevation for Lat: {latitude}, Lon: {longitude} is {elevation:.2f}m") + + return elevation + + except Exception as e: + logging.error(f"GeoElevation: Error in get_point_elevation for ({latitude},{longitude}): {e}", exc_info=True) + return None + finally: + if progress_win_thread: + logging.debug("GeoElevation: Signaling progress dialog to stop.") + progress_win_thread.request_stop() # Signal the Tkinter thread to stop + progress_win_thread.join(timeout=3.0) # Wait for the thread to finish + if progress_win_thread.is_alive(): + logging.warning("GeoElevation: Progress dialog thread did not terminate cleanly after join.") + + +# To make GUI accessible if someone does `from geoelevation import run_gui` +def run_gui_application(): + """Launches the GeoElevation GUI application.""" + from .elevation_gui import ElevationApp # Keep import local to avoid circular deps if GUI imports this module + import tkinter # Alias for clarity + import multiprocessing + + # Important for PyInstaller and multiprocessing, called only when GUI runs + multiprocessing.freeze_support() + + root_window = tkinter.Tk() + try: + app = ElevationApp(parent_widget=root_window) + root_window.mainloop() + except Exception as e_gui: + logging.critical(f"GeoElevation: Failed to run GUI: {e_gui}", exc_info=True) + try: + err_root = tkinter.Tk() + err_root.withdraw() + from tkinter import messagebox # Local import + messagebox.showerror("GUI Startup Error", f"Failed to start GeoElevation GUI:\n{e_gui}") + err_root.destroy() + except Exception: pass \ No newline at end of file diff --git a/geoelevation/__main__.py b/geoelevation/__main__.py index c80b2c8..c6cc5f0 100644 --- a/geoelevation/__main__.py +++ b/geoelevation/__main__.py @@ -1,81 +1,122 @@ -# -*- coding: utf-8 -*- +# geoelevation/__main__.py """ -Main entry point script for the GeoElevation application. -Works for both `python -m geoelevation` and as PyInstaller entry point. -Uses absolute imports. +Main entry point for the GeoElevation package. + +Can be run as a CLI to get point elevation or to launch the GUI. + - CLI mode: python -m geoelevation --lat --lon [--show-progress] + - GUI mode: python -m geoelevation """ +import argparse import sys -import os -import traceback -import multiprocessing -import tkinter as tk +import logging -# --- Import the necessary modules using ABSOLUTE paths --- -try: - # MODIFIED: Changed relative import to absolute import. - # WHY: To make this script work consistently as a direct entry point - # (for PyInstaller) and when run via `python -m`. Assumes the - # 'geoelevation' package is findable in sys.path. - # HOW: Changed 'from .elevation_gui import ElevationApp' to - # 'from geoelevation.elevation_gui import ElevationApp'. - from geoelevation.elevation_gui import ElevationApp - # Import other components if needed for setup (using absolute paths) - # from geoelevation.config import DEFAULT_CACHE_DIR # Example if you had config +# Configure basic logging for CLI and GUI startup +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" +) +logger = logging.getLogger("geoelevation.main") -except ImportError as e: - # Error message adjusted slightly for absolute import context - print(f"ERROR: Could not import required modules using absolute paths (e.g., 'geoelevation.elevation_gui').") - print(f" Ensure the 'geoelevation' package is correctly structured and accessible.") - print(f"ImportError: {e}") - print(f"Current sys.path: {sys.path}") - # Try to determine expected base path for context + +def main_cli(): + """Handles Command Line Interface or launches GUI.""" + parser = argparse.ArgumentParser( + description="GeoElevation Tool: Get point elevation or launch GUI.", + formatter_class=argparse.RawTextHelpFormatter + ) + parser.add_argument( + "--lat", + type=float, + help="Latitude of the point (e.g., 45.0)" + ) + parser.add_argument( + "--lon", + type=float, + help="Longitude of the point (e.g., 7.0)" + ) + parser.add_argument( + "--show-progress", + action="store_true", + help="Display a progress dialog during data retrieval (for CLI point elevation)." + ) + parser.add_argument( + "--verbose", + "-v", + action="store_true", + help="Enable verbose logging output." + ) + parser.add_argument( + "--gui", + action="store_true", + help="Force launch the GUI application (overrides lat/lon arguments)." + ) + + args = parser.parse_args() + + if args.verbose: + logging.getLogger("geoelevation").setLevel(logging.DEBUG) # Set root logger for package + logger.debug("Verbose logging enabled.") + + # Import the main functions from the package's __init__ + # This structure allows __init__.py to be the central point for library functions try: - script_path = os.path.dirname(os.path.abspath(__file__)) - parent_path = os.path.dirname(script_path) - print(f" Expected package 'geoelevation' potentially under: {parent_path}") - except NameError: - pass # __file__ might not be defined in some rare cases - traceback.print_exc() - sys.exit(1) -except Exception as e: - print(f"ERROR: An unexpected error occurred during initial imports: {e}") - traceback.print_exc() - sys.exit(1) - - -def main(): - """ - Initializes and runs the ElevationApp GUI application. - """ - root_window = tk.Tk() - try: - # Instantiate ElevationApp, passing the root window - app = ElevationApp(parent_widget=root_window) - # Start the Tkinter event loop - root_window.mainloop() - except Exception as e: - print(f"FATAL ERROR: An unexpected error occurred during application execution: {e}") - traceback.print_exc() - try: - error_root = tk.Tk() - error_root.withdraw() - from tkinter import messagebox - messagebox.showerror( - "Fatal Error", - f"Application failed to run:\n{e}\n\nSee console for details.", - parent=error_root - ) - error_root.destroy() - except Exception: - pass + from geoelevation import get_point_elevation, run_gui_application + except ImportError as e: + logger.critical( + f"Failed to import core GeoElevation functions. Package might be corrupted or not installed correctly: {e}", + exc_info=True + ) + # Try to give a hint if run from the wrong directory during development + import os + if "geoelevation" not in os.getcwd().lower() and os.path.exists("geoelevation"): + logger.info("Hint: Try running `python -m geoelevation ...` from the directory *above* the 'geoelevation' package folder.") sys.exit(1) -# --- Main Execution Guard --- -if __name__ == "__main__": - # !!! IMPORTANT for PyInstaller and multiprocessing !!! - # Must be called in the main entry script for frozen executables. - multiprocessing.freeze_support() - # print(f"Running GeoElevation via __main__.py...") - main() \ No newline at end of file + if args.gui: + logger.info("Launching GeoElevation GUI (forced by --gui argument)...") + run_gui_application() + elif args.lat is not None and args.lon is not None: + # CLI mode: Get point elevation + logger.info(f"CLI mode: Requesting elevation for Latitude: {args.lat}, Longitude: {args.lon}") + try: + elevation = get_point_elevation( + args.lat, + args.lon, + show_progress_dialog=args.show_progress + ) + + if elevation is None: + print("Elevation: UNAVAILABLE (Data could not be retrieved or error occurred. Check logs.)") + sys.exit(1) # Exit with error for scripting + elif isinstance(elevation, float) and elevation != elevation: # Check for NaN + print("Elevation: NODATA (Point is on a NoData area within the tile)") + else: + print(f"Elevation: {elevation:.2f} meters") + + except RuntimeError as e_rt: # Catch errors from _get_library_manager or Rasterio not found + print(f"ERROR: {e_rt}", file=sys.stderr) + logger.error(f"Runtime error during CLI elevation retrieval: {e_rt}", exc_info=True) + sys.exit(1) + except Exception as e: + print(f"An unexpected error occurred: {e}", file=sys.stderr) + logger.error(f"Unexpected error during CLI elevation retrieval: {e}", exc_info=True) + sys.exit(1) + else: + # Default mode: Launch GUI if no specific CLI elevation request + # (and --gui wasn't used, though that case is handled above) + if args.lat is None and args.lon is None: # Ensure no partial lat/lon args + logger.info("No specific CLI arguments for point elevation. Launching GeoElevation GUI...") + run_gui_application() + else: + parser.print_help() + print("\nError: Both --lat and --lon must be specified for point elevation.", file=sys.stderr) + sys.exit(1) + +if __name__ == "__main__": + # This ensures that when running `python -m geoelevation`, the CLI/GUI logic is executed. + # For PyInstaller, this __main__.py (inside the geoelevation package) + # can now be the entry point. + # multiprocessing.freeze_support() is now called inside run_gui_application if GUI runs. + main_cli() \ No newline at end of file diff --git a/geoelevation/config.py b/geoelevation/config.py new file mode 100644 index 0000000..dac66c4 --- /dev/null +++ b/geoelevation/config.py @@ -0,0 +1 @@ +DEFAULT_CACHE_DIR = "elevation_cache" \ No newline at end of file diff --git a/geoelevation/elevation_gui.py b/geoelevation/elevation_gui.py index bb43053..ae25535 100644 --- a/geoelevation/elevation_gui.py +++ b/geoelevation/elevation_gui.py @@ -438,6 +438,10 @@ if __name__ == "__main__": if not MATPLOTLIB_AVAILABLE: print("WARNING (Test): Matplotlib N/A.") if SCIPY_AVAILABLE: print("INFO (Test): SciPy available.") else: print("WARNING (Test): SciPy N/A (no smooth/interp).") + + # Call freeze_support() here for direct test runs of the GUI module + # This is because run_gui_application (which normally calls it) isn't used here. + multiprocessing.freeze_support() # Important if launching GUI processes root = tk.Tk() try: