diff --git a/src/rtgs_lab_tools/agricultural_modeling/__init__.py b/src/rtgs_lab_tools/agricultural_modeling/__init__.py index 99de75d4..b7610634 100644 --- a/src/rtgs_lab_tools/agricultural_modeling/__init__.py +++ b/src/rtgs_lab_tools/agricultural_modeling/__init__.py @@ -11,31 +11,101 @@ Migrated from rtgsET library. """ -from .crop_parameters import get_crop_names, get_crop_parameters, get_crop_status -from .distance_speed import ( - degrees_to_radians, - feet_to_meters, - meters_per_second_to_miles_per_hour, - miles_per_hour_to_meters_per_second, -) -from .evapotranspiration import ( - calculate_reference_et, - get_required_columns, - validate_input_data, -) -from .growing_degree_days import ( - calculate_corn_heat_units, - calculate_gdd_modified, - calculate_gdd_original, -) -from .temperature import celsius_to_fahrenheit, fahrenheit_to_celsius -from .weather_api import ( - check_missing_dates, - date_chunks, - fetch_weather_data, - validate_coordinates, - validate_date_range, -) +# Heavy dependencies are imported lazily when needed +# This prevents long load times for simple commands like 'rtgs --help' + + +def __getattr__(name): + """Lazy loading of heavy dependencies""" + # Crop parameters + if name == "get_crop_names": + from .crop_parameters import get_crop_names + + return get_crop_names + elif name == "get_crop_parameters": + from .crop_parameters import get_crop_parameters + + return get_crop_parameters + elif name == "get_crop_status": + from .crop_parameters import get_crop_status + + return get_crop_status + # Distance and speed conversions + elif name == "degrees_to_radians": + from .distance_speed import degrees_to_radians + + return degrees_to_radians + elif name == "feet_to_meters": + from .distance_speed import feet_to_meters + + return feet_to_meters + elif name == "meters_per_second_to_miles_per_hour": + from .distance_speed import meters_per_second_to_miles_per_hour + + return meters_per_second_to_miles_per_hour + elif name == "miles_per_hour_to_meters_per_second": + from .distance_speed import miles_per_hour_to_meters_per_second + + return miles_per_hour_to_meters_per_second + # Evapotranspiration (heavy numpy/pandas dependencies) + elif name == "calculate_reference_et": + from .evapotranspiration import calculate_reference_et + + return calculate_reference_et + elif name == "get_required_columns": + from .evapotranspiration import get_required_columns + + return get_required_columns + elif name == "validate_input_data": + from .evapotranspiration import validate_input_data + + return validate_input_data + # Growing degree days + elif name == "calculate_corn_heat_units": + from .growing_degree_days import calculate_corn_heat_units + + return calculate_corn_heat_units + elif name == "calculate_gdd_modified": + from .growing_degree_days import calculate_gdd_modified + + return calculate_gdd_modified + elif name == "calculate_gdd_original": + from .growing_degree_days import calculate_gdd_original + + return calculate_gdd_original + # Temperature conversions (lightweight) + elif name == "celsius_to_fahrenheit": + from .temperature import celsius_to_fahrenheit + + return celsius_to_fahrenheit + elif name == "fahrenheit_to_celsius": + from .temperature import fahrenheit_to_celsius + + return fahrenheit_to_celsius + # Weather API utilities (requests dependency) + elif name == "check_missing_dates": + from .weather_api import check_missing_dates + + return check_missing_dates + elif name == "date_chunks": + from .weather_api import date_chunks + + return date_chunks + elif name == "fetch_weather_data": + from .weather_api import fetch_weather_data + + return fetch_weather_data + elif name == "validate_coordinates": + from .weather_api import validate_coordinates + + return validate_coordinates + elif name == "validate_date_range": + from .weather_api import validate_date_range + + return validate_date_range + else: + raise AttributeError(f"module '{__name__}' has no attribute '{name}'") + __all__ = [ # Temperature conversions diff --git a/src/rtgs_lab_tools/data_parser/__init__.py b/src/rtgs_lab_tools/data_parser/__init__.py index a7dd6b0d..8b15f3d8 100644 --- a/src/rtgs_lab_tools/data_parser/__init__.py +++ b/src/rtgs_lab_tools/data_parser/__init__.py @@ -1,5 +1,17 @@ """Universal JSON packet parser for historical packets based on device JSON schema tool for RTGS Lab Tools.""" -from .core import parse_gems_data +# Heavy dependencies are imported lazily when needed +# This prevents long load times for simple commands like 'rtgs --help' + + +def __getattr__(name): + """Lazy loading of heavy dependencies""" + if name == "parse_gems_data": + from .core import parse_gems_data + + return parse_gems_data + else: + raise AttributeError(f"module '{__name__}' has no attribute '{name}'") + __all__ = ["parse_gems_data"] diff --git a/src/rtgs_lab_tools/device_configuration/__init__.py b/src/rtgs_lab_tools/device_configuration/__init__.py index 859ea1f9..a9818250 100644 --- a/src/rtgs_lab_tools/device_configuration/__init__.py +++ b/src/rtgs_lab_tools/device_configuration/__init__.py @@ -1,7 +1,25 @@ """Device configuration management tools for RTGS Lab Tools.""" -from .cli import device_configuration_cli -from .particle_client import ParticleClient -from .update_configuration import ParticleConfigUpdater +# Heavy dependencies are imported lazily when needed +# This prevents long load times for simple commands like 'rtgs --help' + + +def __getattr__(name): + """Lazy loading of heavy dependencies""" + if name == "device_configuration_cli": + from .cli import device_configuration_cli + + return device_configuration_cli + elif name == "ParticleClient": + from .particle_client import ParticleClient + + return ParticleClient + elif name == "ParticleConfigUpdater": + from .update_configuration import ParticleConfigUpdater + + return ParticleConfigUpdater + else: + raise AttributeError(f"module '{__name__}' has no attribute '{name}'") + __all__ = ["device_configuration_cli", "ParticleConfigUpdater", "ParticleClient"] diff --git a/src/rtgs_lab_tools/gridded_data/__init__.py b/src/rtgs_lab_tools/gridded_data/__init__.py index 5fba05e8..ed498048 100644 --- a/src/rtgs_lab_tools/gridded_data/__init__.py +++ b/src/rtgs_lab_tools/gridded_data/__init__.py @@ -1,10 +1,57 @@ """Gridded climate data access tools for RTGS Lab Tools.""" -# Import sources for immediate availability -from .utils import sources +# Only import lightweight utilities immediately +from .utils import load_roi_json, sources + +# Heavy dependencies are imported lazily when needed +# This prevents long load times for simple commands like 'rtgs --help' + + +def __getattr__(name): + """Lazy loading of heavy dependencies""" + if name == "init_ee": + from .gee import init_ee + + return init_ee + elif name == "list_GEE_vars": + from .gee import list_GEE_vars + + return list_GEE_vars + elif name == "load_roi": + from .gee import load_roi + + return load_roi + elif name == "search_images": + from .gee import search_images + + return search_images + elif name == "download_GEE_point": + from .gee import download_GEE_point + + return download_GEE_point + elif name == "download_GEE_raster": + from .gee import download_GEE_raster + + return download_GEE_raster + elif name == "quick_search": + from .planet import quick_search + + return quick_search + elif name == "download_scenes": + from .planet import download_scenes + + return download_scenes + elif name == "download_clipped_scenes": + from .planet import download_clipped_scenes + + return download_clipped_scenes + elif name == "extract_time_series": + from .processors import extract_time_series + + return extract_time_series + else: + raise AttributeError(f"module '{__name__}' has no attribute '{name}'") -# Empty __init__.py to avoid loading heavy dependencies at import time -# Functions are imported directly from submodules when needed __all__ = [ "download_GEE_raster", diff --git a/src/rtgs_lab_tools/gridded_data/cli.py b/src/rtgs_lab_tools/gridded_data/cli.py index bed2aae2..ce9aa9e9 100644 --- a/src/rtgs_lab_tools/gridded_data/cli.py +++ b/src/rtgs_lab_tools/gridded_data/cli.py @@ -67,11 +67,11 @@ def download_clipped_scenes( cli_ctx.setup("download-scenes", verbose, log_file, no_postgres_log) try: - from ..gridded_data import download_clipped_scenes, load_roi + from ..gridded_data import download_clipped_scenes, load_roi_json - # Load ROI from file + # Load ROI from file (Planet Labs doesn't use Earth Engine) if roi: - roi = load_roi(roi).getInfo() + roi = load_roi_json(roi) download_clipped_scenes( source=source, @@ -79,7 +79,7 @@ def download_clipped_scenes( roi=roi, start_date=start_date, end_date=end_date, - clouds=clouds, + clouds=int(clouds) if clouds else None, out_dir=out_dir, ) @@ -141,11 +141,11 @@ def download_scenes( cli_ctx.setup("download-scenes", verbose, log_file, no_postgres_log) try: - from ..gridded_data import download_scenes, load_roi + from ..gridded_data import download_scenes, load_roi_json - # Load ROI from file + # Load ROI from file (Planet Labs doesn't use Earth Engine) if roi: - roi = load_roi(roi).getInfo() + roi = load_roi_json(roi) download_scenes( source=source, @@ -153,7 +153,7 @@ def download_scenes( roi=roi, start_date=start_date, end_date=end_date, - clouds=clouds, + clouds=int(clouds) if clouds else None, out_dir=out_dir, ) @@ -211,11 +211,11 @@ def planet_search( cli_ctx.setup("planet-search", verbose, log_file, no_postgres_log) try: - from ..gridded_data import load_roi, quick_search + from ..gridded_data import load_roi_json, quick_search - # Load ROI from file + # Load ROI from file (Planet Labs doesn't use Earth Engine) if roi: - roi = load_roi(roi).getInfo() + roi = load_roi_json(roi) # print(roi_bounds) quick_search( @@ -223,7 +223,7 @@ def planet_search( roi=roi, start_date=start_date, end_date=end_date, - clouds=clouds, + clouds=int(clouds) if clouds else None, out_dir=out_dir, ) diff --git a/src/rtgs_lab_tools/gridded_data/gee.py b/src/rtgs_lab_tools/gridded_data/gee.py index b76a4c23..3452403e 100644 --- a/src/rtgs_lab_tools/gridded_data/gee.py +++ b/src/rtgs_lab_tools/gridded_data/gee.py @@ -198,6 +198,10 @@ def download_GEE_point(name, source, bands, roi, start_date, end_date, out_dir): size = collection.size().getInfo() print(f"Found {size} images to process") + if size == 0: + print("No images found for the specified date range and location") + return None + scale = collection.first().select(bands[0]).projection().nominalScale().getInfo() os.makedirs(out_dir, exist_ok=True) @@ -245,6 +249,7 @@ def clip_img(img): roi = roi.geometry() + qa_band = None # Initialize qa_band for climate datasets if name in qa_bands.keys(): qa_band = qa_bands[name] diff --git a/src/rtgs_lab_tools/gridded_data/utils.py b/src/rtgs_lab_tools/gridded_data/utils.py index 6c22b097..9fdba1d2 100644 --- a/src/rtgs_lab_tools/gridded_data/utils.py +++ b/src/rtgs_lab_tools/gridded_data/utils.py @@ -21,3 +21,14 @@ "MYD": "state_1km", # Aqua "VIIRS": "QF1", } + + +def load_roi_json(path): + """Load ROI as plain JSON for Planet Labs (no Earth Engine)""" + import json + + with open(path) as f: + roi_geom = json.load(f) + + # Convert to format Planet Labs functions expect + return {"features": [{"geometry": roi_geom}]} diff --git a/src/rtgs_lab_tools/sensing_data/__init__.py b/src/rtgs_lab_tools/sensing_data/__init__.py index 7372f4b4..05b04a94 100644 --- a/src/rtgs_lab_tools/sensing_data/__init__.py +++ b/src/rtgs_lab_tools/sensing_data/__init__.py @@ -1,13 +1,42 @@ """Sensing data tools for RTGS Lab Tools.""" -from .data_extractor import ( - extract_data, - get_nodes_for_project, - get_raw_data, - list_available_projects, - list_projects, -) -from .file_operations import create_zip_archive, save_data +# Heavy dependencies are imported lazily when needed +# This prevents long load times for simple commands like 'rtgs --help' + + +def __getattr__(name): + """Lazy loading of heavy dependencies""" + if name == "extract_data": + from .data_extractor import extract_data + + return extract_data + elif name == "get_nodes_for_project": + from .data_extractor import get_nodes_for_project + + return get_nodes_for_project + elif name == "get_raw_data": + from .data_extractor import get_raw_data + + return get_raw_data + elif name == "list_available_projects": + from .data_extractor import list_available_projects + + return list_available_projects + elif name == "list_projects": + from .data_extractor import list_projects + + return list_projects + elif name == "create_zip_archive": + from .file_operations import create_zip_archive + + return create_zip_archive + elif name == "save_data": + from .file_operations import save_data + + return save_data + else: + raise AttributeError(f"module '{__name__}' has no attribute '{name}'") + __all__ = [ "extract_data", diff --git a/src/rtgs_lab_tools/visualization/__init__.py b/src/rtgs_lab_tools/visualization/__init__.py index 792f3443..1b2a2f07 100644 --- a/src/rtgs_lab_tools/visualization/__init__.py +++ b/src/rtgs_lab_tools/visualization/__init__.py @@ -1,15 +1,38 @@ """Visualization tools for RTGS Lab Tools.""" -from .data_utils import ( - detect_data_type, - get_available_measurements, - load_and_prepare_data, -) -from .time_series import ( - create_multi_parameter_plot, - create_time_series_plot, - plot_sensor_data, -) +# Heavy dependencies are imported lazily when needed +# This prevents long load times for simple commands like 'rtgs --help' + + +def __getattr__(name): + """Lazy loading of heavy dependencies""" + if name == "create_time_series_plot": + from .time_series import create_time_series_plot + + return create_time_series_plot + elif name == "create_multi_parameter_plot": + from .time_series import create_multi_parameter_plot + + return create_multi_parameter_plot + elif name == "plot_sensor_data": + from .time_series import plot_sensor_data + + return plot_sensor_data + elif name == "detect_data_type": + from .data_utils import detect_data_type + + return detect_data_type + elif name == "get_available_measurements": + from .data_utils import get_available_measurements + + return get_available_measurements + elif name == "load_and_prepare_data": + from .data_utils import load_and_prepare_data + + return load_and_prepare_data + else: + raise AttributeError(f"module '{__name__}' has no attribute '{name}'") + __all__ = [ "create_time_series_plot",