add command line and use in module function
This commit is contained in:
parent
8b5f620c66
commit
f18a23df41
260
README.md
260
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 <latitudine> --lon <longitudine> [--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: <valore> 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 <latitude> --lon <longitude> [--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: <value> 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)*
|
||||
|
||||
@ -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
|
||||
@ -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 <latitude> --lon <longitude> [--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()
|
||||
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()
|
||||
1
geoelevation/config.py
Normal file
1
geoelevation/config.py
Normal file
@ -0,0 +1 @@
|
||||
DEFAULT_CACHE_DIR = "elevation_cache"
|
||||
@ -439,6 +439,10 @@ if __name__ == "__main__":
|
||||
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:
|
||||
app = ElevationApp(root)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user