212 lines
11 KiB
Python
212 lines
11 KiB
Python
# geoelevation/__main__.py
|
|
"""
|
|
Main entry point for the GeoElevation package when executed as a module
|
|
(e.g., `python -m geoelevation`).
|
|
|
|
This script handles command-line arguments to either:
|
|
1. Retrieve and print the elevation for a specific geographic point (CLI mode).
|
|
2. Launch the GeoElevation Graphical User Interface (GUI mode).
|
|
"""
|
|
|
|
# Standard library imports
|
|
import argparse
|
|
import sys
|
|
import logging # For configuring logging at the application's entry point
|
|
import os # For providing hints in case of import errors during development
|
|
import math # For math.isnan in CLI output handling
|
|
|
|
# --- Application Entry Point Logging Configuration ---
|
|
# Configure basic logging for the entire application session when this __main__.py is run.
|
|
# This setup will apply if the package is run as an application.
|
|
# If 'geoelevation' is imported as a library, its __init__.py configures a NullHandler
|
|
# by default, and the consuming application should set up its own logging.
|
|
logging.basicConfig(
|
|
level=logging.INFO, # Default level, can be overridden by the --verbose argument
|
|
format="%(asctime)s - %(levelname)s - [%(name)s] - %(message)s", # Include logger name
|
|
datefmt="%Y-%m-%d %H:%M:%S"
|
|
)
|
|
# Create a logger specific to this main entry script for its own messages
|
|
main_entry_logger = logging.getLogger("geoelevation.cli_entry")
|
|
|
|
|
|
def SCRIPT_ENTRY_POINT_ROUTINE() -> None: # PEP8: function names should be lowercase_with_underscores
|
|
"""
|
|
Parses command-line arguments and either performs a CLI action (get point elevation)
|
|
or launches the main GUI application.
|
|
This is the main operational function called when the script is executed.
|
|
"""
|
|
cli_argument_parser = argparse.ArgumentParser(
|
|
description="GeoElevation Tool: Retrieve elevation for a point via CLI or launch the GUI.",
|
|
formatter_class=argparse.RawTextHelpFormatter, # Allows for newlines in epilog/help
|
|
epilog=(
|
|
"Usage Examples:\n"
|
|
" python -m geoelevation --lat 45.0 --lon 7.0\n"
|
|
" python -m geoelevation --lat 45.0 --lon 7.0 --show-progress\n"
|
|
" python -m geoelevation --gui\n"
|
|
" python -m geoelevation (launches GUI by default if no point coordinates are specified)"
|
|
)
|
|
)
|
|
cli_argument_parser.add_argument(
|
|
"--lat",
|
|
type=float,
|
|
metavar="LATITUDE",
|
|
help="Latitude of the target point in decimal degrees (e.g., 45.0)."
|
|
)
|
|
cli_argument_parser.add_argument(
|
|
"--lon",
|
|
type=float,
|
|
metavar="LONGITUDE",
|
|
help="Longitude of the target point in decimal degrees (e.g., 7.0)."
|
|
)
|
|
cli_argument_parser.add_argument(
|
|
"--show-progress",
|
|
action="store_true", # If present, args.show_progress will be True
|
|
help="Display a visual progress dialog during data retrieval (applies to CLI point elevation mode)."
|
|
)
|
|
cli_argument_parser.add_argument(
|
|
"--verbose",
|
|
"-v",
|
|
action="store_true",
|
|
help="Enable verbose (DEBUG level) logging output for the entire 'geoelevation' package."
|
|
)
|
|
cli_argument_parser.add_argument(
|
|
"--gui",
|
|
action="store_true",
|
|
help="Force the launch of the GUI application, overriding other CLI arguments if present."
|
|
)
|
|
|
|
# Parse the command-line arguments provided by the user
|
|
parsed_command_line_args = cli_argument_parser.parse_args()
|
|
|
|
# Configure logging level based on verbosity argument
|
|
if parsed_command_line_args.verbose:
|
|
# Set the logging level for the root logger of the 'geoelevation' package to DEBUG
|
|
# This will affect all loggers within this package unless they have a more specific level set.
|
|
logging.getLogger("geoelevation").setLevel(logging.DEBUG)
|
|
main_entry_logger.debug("Verbose logging (DEBUG level) has been enabled for the 'geoelevation' package.")
|
|
else:
|
|
# Ensure the 'geoelevation' package logger is at least INFO if not verbose.
|
|
# This prevents it from being too quiet if the root logger (from basicConfig)
|
|
# was set to WARNING or ERROR by another part of a larger system (unlikely here).
|
|
geoelevation_package_logger = logging.getLogger("geoelevation")
|
|
if geoelevation_package_logger.getEffectiveLevel() > logging.INFO:
|
|
geoelevation_package_logger.setLevel(logging.INFO)
|
|
|
|
|
|
# --- Import Public API Functions ---
|
|
# Attempt to import the primary public API functions from the geoelevation package's __init__.py.
|
|
# This is the recommended way for an application entry point to access package functionality.
|
|
try:
|
|
from geoelevation import get_point_elevation # API for CLI
|
|
from geoelevation import run_gui_application # API for launching GUI
|
|
except ImportError as e_fatal_api_import:
|
|
main_entry_logger.critical(
|
|
f"CRITICAL FAILURE: Could not import core API functions (get_point_elevation, run_gui_application) "
|
|
f"from the GeoElevation package. This indicates a fundamental problem with the package "
|
|
f"installation, structure, or missing critical dependencies. Original Error: {e_fatal_api_import}",
|
|
exc_info=True # Include traceback for critical errors
|
|
)
|
|
# Provide a hint to the user if they might be running the command from an incorrect directory
|
|
# during development (e.g., inside the package folder instead of its parent).
|
|
current_dir = os.getcwd()
|
|
if "geoelevation" not in current_dir.lower() and \
|
|
os.path.isdir(os.path.join(os.path.dirname(current_dir), "geoelevation")): # Heuristic
|
|
main_entry_logger.info(
|
|
"DEVELOPMENT HINT: If running with `python -m geoelevation ...`, "
|
|
"ensure you are in the directory *above* the 'geoelevation' package folder."
|
|
)
|
|
sys.exit(1) # Exit with an error code if core API cannot be loaded
|
|
|
|
# --- Determine Action Based on Parsed Arguments ---
|
|
if parsed_command_line_args.gui:
|
|
main_entry_logger.info("Launching GeoElevation GUI (explicitly requested via --gui argument)...")
|
|
run_gui_application()
|
|
elif parsed_command_line_args.lat is not None and parsed_command_line_args.lon is not None:
|
|
# CLI mode for getting point elevation
|
|
main_entry_logger.info(
|
|
f"CLI mode: Requesting elevation for Latitude: {parsed_command_line_args.lat}, "
|
|
f"Longitude: {parsed_command_line_args.lon}"
|
|
)
|
|
try:
|
|
# Call the public API function to get the elevation
|
|
elevation_result = get_point_elevation(
|
|
latitude=parsed_command_line_args.lat,
|
|
longitude=parsed_command_line_args.lon,
|
|
show_progress_dialog_flag=parsed_command_line_args.show_progress
|
|
)
|
|
|
|
# Print the result to standard output for CLI users
|
|
if elevation_result is None:
|
|
# This indicates an issue like data unavailability or an internal error.
|
|
print("Elevation: UNAVAILABLE (Data could not be retrieved or an error occurred. Please check logs for details.)")
|
|
sys.exit(1) # Exit with a non-zero code to indicate error for scripting
|
|
elif isinstance(elevation_result, float) and math.isnan(elevation_result):
|
|
# Point is on a NoData area within a valid DEM tile
|
|
print("Elevation: NODATA (Point is located on a NoData area within the DEM tile)")
|
|
sys.exit(0) # Successful query, result is NoData
|
|
else:
|
|
# Successful elevation retrieval
|
|
print(f"Elevation: {elevation_result:.2f} meters")
|
|
sys.exit(0) # Successful CLI operation
|
|
|
|
except RuntimeError as e_cli_runtime_get_elev:
|
|
# Catch RuntimeErrors (e.g., Rasterio missing, ElevationManager init failure)
|
|
print(f"CLI ERROR: A critical runtime error occurred: {e_cli_runtime_get_elev}", file=sys.stderr)
|
|
main_entry_logger.error(
|
|
f"Runtime error during CLI elevation retrieval: {e_cli_runtime_get_elev}", exc_info=True
|
|
)
|
|
sys.exit(1)
|
|
except ValueError as e_cli_value_get_elev:
|
|
# Catch ValueErrors (e.g., invalid coordinates passed to API that were not caught by argparse)
|
|
print(f"CLI ERROR: Invalid input value provided - {e_cli_value_get_elev}", file=sys.stderr)
|
|
main_entry_logger.error(
|
|
f"ValueError during CLI elevation retrieval: {e_cli_value_get_elev}", exc_info=False # Traceback often not needed for user input errors
|
|
)
|
|
sys.exit(1)
|
|
except Exception as e_cli_unexpected_get_elev:
|
|
# Catch any other unexpected exceptions during the API call
|
|
print(f"CLI ERROR: An unexpected error occurred during elevation retrieval: {e_cli_unexpected_get_elev}", file=sys.stderr)
|
|
main_entry_logger.error(
|
|
f"Unexpected error during CLI elevation retrieval: {e_cli_unexpected_get_elev}", exc_info=True
|
|
)
|
|
sys.exit(1)
|
|
else:
|
|
# Default behavior: Launch GUI if no CLI action for point elevation is fully specified
|
|
# This condition also catches cases where only --lat or only --lon was given without the other.
|
|
if parsed_command_line_args.lat is None and parsed_command_line_args.lon is None:
|
|
# No lat/lon args, and --gui was not specified. Default to GUI.
|
|
main_entry_logger.info(
|
|
"No specific CLI arguments for point elevation were provided. "
|
|
"Launching GeoElevation GUI by default..."
|
|
)
|
|
run_gui_application()
|
|
else:
|
|
# Partial arguments for point elevation (e.g., only --lat without --lon, or vice-versa)
|
|
cli_argument_parser.print_help(sys.stderr) # Print help message to standard error
|
|
# Add a specific error message for this case
|
|
print(
|
|
"\nError: For Command Line Interface (CLI) point elevation retrieval, "
|
|
"both --lat and --lon arguments must be specified together.",
|
|
file=sys.stderr
|
|
)
|
|
sys.exit(2) # Use a different exit code for incorrect CLI usage
|
|
|
|
|
|
# --- Main Execution Guard ---
|
|
if __name__ == "__main__":
|
|
# This block is executed when the script is run as the main program, e.g.:
|
|
# `python -m geoelevation`
|
|
# It can also be the entry point for an executable created by PyInstaller
|
|
# if `geoelevation/__main__.py` is specified.
|
|
|
|
# Note: `multiprocessing.freeze_support()` is now strategically placed within
|
|
# `run_gui_application` (inside `geoelevation/__init__.py`) because it's only
|
|
# needed if the GUI (which might use multiprocessing for its tasks) is launched.
|
|
# This keeps `__main__.py` cleaner and ensures `freeze_support` is called
|
|
# at the appropriate juncture before any GUI-related processes might be spawned.
|
|
|
|
main_entry_logger.debug(
|
|
f"GeoElevation __main__.py executed as entry point. Python version: {sys.version_info.major}.{sys.version_info.minor}."
|
|
"Proceeding to CLI/GUI launch logic."
|
|
)
|
|
SCRIPT_ENTRY_POINT_ROUTINE() # Call the main operational function |