Source code for hyplan

"""
HyPlan - Planning software for airborne remote sensing science campaigns.

Core objects are re-exported here for convenience and loaded lazily on
first access (PEP 562), so ``import hyplan`` stays fast::

    from hyplan import FlightLine, box_around_polygon, Airport, ureg
    from hyplan import KingAirB200, AVIRIS3

Specialized modules (clouds, terrain, satellites, glint, sun) should be
imported directly::

    from hyplan.clouds import create_cloud_data_array_with_limit
    from hyplan.terrain import download_dem_files

Plotting naming convention: ``map_*`` functions return interactive
Folium maps (:func:`hyplan.map_flight_lines`, :func:`hyplan.map_airspace`,
:func:`hyplan.map_isochrone`) while ``plot_*`` functions return static
Matplotlib figures (:func:`hyplan.plot_airspace_map`,
:func:`hyplan.plot_isochrone_static`).
"""

import importlib
import logging as _logging
from typing import TYPE_CHECKING, Any

try:
    from ._version import version as __version__
except ImportError:
    # Package not installed via setuptools-scm (e.g. editable dev install
    # before first build), fall back to a default.
    __version__ = "0.0.0.dev0"

# Exceptions are the only eager re-export: they're dependency-free and
# users need them resolvable in `except` clauses without side effects.
from .exceptions import (
    HyPlanError,
    HyPlanRuntimeError,
    HyPlanTypeError,
    HyPlanValueError,
)


[docs] def setup_logging( level: int = _logging.INFO, format: str = "%(asctime)s %(name)s %(levelname)s: %(message)s", ) -> None: """Attach a StreamHandler to the ``hyplan`` logger. Library code uses ``logging.getLogger(__name__)`` everywhere and never configures handlers itself. Call this once from a notebook, script, or CLI to see hyplan's INFO/WARNING messages. Idempotent — re-calling replaces the handler instead of stacking duplicates. """ logger = _logging.getLogger("hyplan") for h in list(logger.handlers): if getattr(h, "_hyplan_managed", False): logger.removeHandler(h) handler = _logging.StreamHandler() handler.setFormatter(_logging.Formatter(format)) handler._hyplan_managed = True # type: ignore[attr-defined] # custom attr on logging.Handler logger.addHandler(handler) logger.setLevel(level) logger.propagate = False
# --- Lazy core re-exports (PEP 562) --- # Owning submodule -> names re-exported at the top level. This is the # single source of truth for the lazy loader; keep it in sync with # ``__all__`` when adding names. _LAZY_MODULES: dict[str, tuple[str, ...]] = { ".aircraft": ( "DLR_HALO", "NASA_B777", "NASA_C20A", "NASA_C130", "NASA_ER2", "NASA_GIII", "NASA_GIV", "NASA_GV", "NASA_P3", "NASA_WB57", "NERC_DO228", "NOAA_GIV", "NOAA_WP3D", "SAFIRE_ATR42", "Aircraft", "AWI_BaslerBT67", "BAS_TwinOtter", "FAAM_BAe146", "KingAir350", "KingAirA90", "KingAirB200", "NOAA_TwinOtter", ), ".airports": ( "Airport", "airports_within_radius", "find_nearest_airport", "find_nearest_airports", "initialize_data", ), ".airspace": ( "Airspace", "AirspaceConflict", "FAATFRClient", "FlightPlanDBClient", "NASRAirspaceSource", "OceanicTrack", "OpenAIPClient", "check_airspace_conflicts", "check_airspace_proximity", "classify_severity", "clear_airspace_cache", "convert_agl_floors", "fetch_and_check", "filter_by_schedule", "summarize_airspaces", ), ".campaign": ("Campaign",), ".dubins3d": ("DubinsPath2D",), ".exports": ( "to_er2_csv", "to_excel", "to_foreflight_csv", "to_gpx", "to_honeywell_fms", "to_icartt", "to_kml", "to_pilot_excel", "to_trackair", "to_txt", ), ".flight_box": ( "altitude_msl_for_pixel_size", "box_around_center_line", "box_around_center_terrain", "box_around_polygon", "box_around_polygon_terrain", ), ".flight_line": ("FlightLine",), ".flight_optimizer": ("build_graph", "greedy_optimize"), ".flight_patterns": ( "coordinated_line", "flight_lines_to_waypoint_path", "polygon", "racetrack", "rosette", "sawtooth", "spiral", ), ".instruments": ( "AVAPS_NRD41", "AVIRIS3", "AVIRIS5", "AXCTD", "CPL", "GCAS_VNIR", "GLIHT_DUAL_VQ_480I", "GLIHT_HRAC", "GLIHT_THERMAL", "HALO", "HSRL2", "LVIS", "LVIS_LENS_MEDIUM", "LVIS_LENS_NARROW", "LVIS_LENS_WIDE", "LVIS_LENSES", "MASTER", "PICARD", "PRISM", "RD94", "RIEGL_VQ_480II", "SENSOR_REGISTRY", "AerosolWindProfiler", "AircraftTrackSample", "ALSLidar", "AVIRISClassic", "AVIRISNextGen", "ContiguityError", "DropsondePlan", "DropsondeRelease", "DropsondeReleaseSolution", "DropsondeSystem", "DropsondeTrajectory", "FlightPlanTrack", "FrameCamera", "GCAS_UV_Vis", "GLiHT_SIF", "GLiHT_SWIR", "GLiHT_VNIR", "HyTES", "LidarMount", "LineScanner", "LVISLens", "MultiALSLidarRig", "MultiCameraRig", "PlannedSegment", "ProfilingLidar", "RadarExclusionConflict", "Sensor", "SidelookingRadar", "UAVSAR_Kaband", "UAVSAR_Lband", "UAVSAR_Pband", "awp_profile_locations_for_flight_line", "awp_profile_locations_for_plan", "check_lband_radar_exclusions", "create_sensor", "eMAS", "flag_awp_stable_segments", "register_sensor", "releases_along_flight_line", "simulate_descent_trajectory", "simulate_release", "solve_release_for_target", "summarize_trajectories", "terminal_velocity_nrd41", "terminal_velocity_sippican_axctd", ), ".pattern": ("Pattern",), ".planning": ( "compute_concentric_isochrones", "compute_flight_plan", "compute_isochrone", "compute_multi_base_isochrone", "compute_multi_refuel_isochrone", "compute_refuel_isochrone", "evaluate_target_reachability", "flag_below_min_safe_speed", "isochrone_polygon", "plot_isochrone", ), ".plotting": ( "map_airspace", "map_flight_lines", "plot_airspace_map", "plot_altitude_trajectory", "plot_conflict_matrix", "plot_flight_plan", "plot_isochrone_static", "plot_oceanic_tracks", "plot_vertical_profile", "terrain_profile_along_track", ), ".swath": ( "analyze_swath_gaps_overlaps", "calculate_swath_widths", "generate_swath_polygon", ), ".units": ( "altitude_to_flight_level", "convert_angle", "convert_distance", "convert_speed", "convert_time", "ureg", ), ".waypoint": ("Waypoint",), ".winds": ( "ConstantWindField", "GFSWindField", "GMAOWindField", "MERRA2WindField", "StillAirField", "WindField", "wind_field_from_plan", ), } _LAZY_ATTRS: dict[str, str] = { name: module for module, names in _LAZY_MODULES.items() for name in names } # Top-level names whose attribute in the owning module differs. _LAZY_ALIASES: dict[str, tuple[str, str]] = { # Interactive Folium isochrone plotter under the map_* convention. "map_isochrone": (".planning", "plot_isochrone"), } if TYPE_CHECKING: from .aircraft import ( DLR_HALO, NASA_B777, NASA_C20A, NASA_C130, NASA_ER2, NASA_GIII, NASA_GIV, NASA_GV, NASA_P3, NASA_WB57, NERC_DO228, NOAA_GIV, NOAA_WP3D, SAFIRE_ATR42, Aircraft, AWI_BaslerBT67, BAS_TwinOtter, FAAM_BAe146, KingAir350, KingAirA90, KingAirB200, NOAA_TwinOtter, ) from .airports import ( Airport, airports_within_radius, find_nearest_airport, find_nearest_airports, initialize_data, ) from .airspace import ( Airspace, AirspaceConflict, FAATFRClient, FlightPlanDBClient, NASRAirspaceSource, OceanicTrack, OpenAIPClient, check_airspace_conflicts, check_airspace_proximity, classify_severity, clear_airspace_cache, convert_agl_floors, fetch_and_check, filter_by_schedule, summarize_airspaces, ) from .campaign import Campaign from .dubins3d import DubinsPath2D from .exports import ( to_er2_csv, to_excel, to_foreflight_csv, to_gpx, to_honeywell_fms, to_icartt, to_kml, to_pilot_excel, to_trackair, to_txt, ) from .flight_box import ( altitude_msl_for_pixel_size, box_around_center_line, box_around_center_terrain, box_around_polygon, box_around_polygon_terrain, ) from .flight_line import FlightLine from .flight_optimizer import build_graph, greedy_optimize from .flight_patterns import ( coordinated_line, flight_lines_to_waypoint_path, polygon, racetrack, rosette, sawtooth, spiral, ) from .instruments import ( AVAPS_NRD41, AVIRIS3, AVIRIS5, AXCTD, CPL, GCAS_VNIR, GLIHT_DUAL_VQ_480I, GLIHT_HRAC, GLIHT_THERMAL, HALO, HSRL2, LVIS, LVIS_LENS_MEDIUM, LVIS_LENS_NARROW, LVIS_LENS_WIDE, LVIS_LENSES, MASTER, PICARD, PRISM, RD94, RIEGL_VQ_480II, SENSOR_REGISTRY, AerosolWindProfiler, AircraftTrackSample, ALSLidar, AVIRISClassic, AVIRISNextGen, ContiguityError, DropsondePlan, DropsondeRelease, DropsondeReleaseSolution, DropsondeSystem, DropsondeTrajectory, FlightPlanTrack, FrameCamera, GCAS_UV_Vis, GLiHT_SIF, GLiHT_SWIR, GLiHT_VNIR, HyTES, LidarMount, LineScanner, LVISLens, MultiALSLidarRig, MultiCameraRig, PlannedSegment, ProfilingLidar, RadarExclusionConflict, Sensor, SidelookingRadar, UAVSAR_Kaband, UAVSAR_Lband, UAVSAR_Pband, awp_profile_locations_for_flight_line, awp_profile_locations_for_plan, check_lband_radar_exclusions, create_sensor, eMAS, flag_awp_stable_segments, register_sensor, releases_along_flight_line, simulate_descent_trajectory, simulate_release, solve_release_for_target, summarize_trajectories, terminal_velocity_nrd41, terminal_velocity_sippican_axctd, ) from .pattern import Pattern from .planning import ( compute_concentric_isochrones, compute_flight_plan, compute_isochrone, compute_multi_base_isochrone, compute_multi_refuel_isochrone, compute_refuel_isochrone, evaluate_target_reachability, flag_below_min_safe_speed, isochrone_polygon, plot_isochrone, ) from .planning import plot_isochrone as map_isochrone from .plotting import ( map_airspace, map_flight_lines, plot_airspace_map, plot_altitude_trajectory, plot_conflict_matrix, plot_flight_plan, plot_isochrone_static, plot_oceanic_tracks, plot_vertical_profile, terrain_profile_along_track, ) from .swath import ( analyze_swath_gaps_overlaps, calculate_swath_widths, generate_swath_polygon, ) from .units import ( altitude_to_flight_level, convert_angle, convert_distance, convert_speed, convert_time, ureg, ) from .waypoint import Waypoint from .winds import ( ConstantWindField, GFSWindField, GMAOWindField, MERRA2WindField, StillAirField, WindField, wind_field_from_plan, ) def __getattr__(name: str) -> Any: if name in _LAZY_ATTRS: module = importlib.import_module(_LAZY_ATTRS[name], __name__) attr: Any = getattr(module, name) elif name in _LAZY_ALIASES: module_name, attr_name = _LAZY_ALIASES[name] module = importlib.import_module(module_name, __name__) attr = getattr(module, attr_name) elif not name.startswith("_"): # Plain submodule access (e.g. ``hyplan.plotting`` after a bare # ``import hyplan``) — the eager imports used to provide this. try: attr = importlib.import_module(f".{name}", __name__) except ModuleNotFoundError as err: if err.name != f"{__name__}.{name}": raise raise AttributeError( f"module {__name__!r} has no attribute {name!r}" ) from None else: raise AttributeError(f"module {__name__!r} has no attribute {name!r}") globals()[name] = attr return attr def __dir__() -> list[str]: return sorted(set(__all__) | set(globals())) __all__ = [ "AVAPS_NRD41", "AVIRIS3", "AVIRIS5", "AXCTD", "CPL", "DLR_HALO", "GCAS_VNIR", "GLIHT_DUAL_VQ_480I", "GLIHT_HRAC", "GLIHT_THERMAL", "HALO", "HSRL2", "LVIS", "LVIS_LENSES", "LVIS_LENS_MEDIUM", "LVIS_LENS_NARROW", "LVIS_LENS_WIDE", "MASTER", "NASA_B777", "NASA_C20A", "NASA_C130", "NASA_ER2", "NASA_GIII", "NASA_GIV", "NASA_GV", "NASA_P3", "NASA_WB57", "NERC_DO228", "NOAA_GIV", "NOAA_WP3D", "PICARD", "PRISM", "RD94", "RIEGL_VQ_480II", "SAFIRE_ATR42", "SENSOR_REGISTRY", "ALSLidar", "AVIRISClassic", "AVIRISNextGen", "AWI_BaslerBT67", "AerosolWindProfiler", # Aircraft "Aircraft", "AircraftTrackSample", # Airports "Airport", # Airspace "Airspace", "AirspaceConflict", "BAS_TwinOtter", # Campaign "Campaign", "ConstantWindField", "ContiguityError", "DropsondePlan", "DropsondeRelease", "DropsondeReleaseSolution", "DropsondeSystem", "DropsondeTrajectory", "DubinsPath2D", "FAAM_BAe146", "FAATFRClient", # Flight geometry "FlightLine", "FlightPlanDBClient", "FlightPlanTrack", "FrameCamera", "GCAS_UV_Vis", "GFSWindField", "GLiHT_SIF", "GLiHT_SWIR", "GLiHT_VNIR", "GMAOWindField", # Exceptions "HyPlanError", "HyPlanRuntimeError", "HyPlanTypeError", "HyPlanValueError", "HyTES", "KingAir350", "KingAirA90", "KingAirB200", "LVISLens", "LidarMount", "LineScanner", "MERRA2WindField", "MultiALSLidarRig", "MultiCameraRig", "NASRAirspaceSource", "NOAA_TwinOtter", "OceanicTrack", "OpenAIPClient", "Pattern", "PlannedSegment", "ProfilingLidar", "RadarExclusionConflict", # Sensors "Sensor", "SidelookingRadar", "StillAirField", "UAVSAR_Kaband", "UAVSAR_Lband", "UAVSAR_Pband", # Dubins "Waypoint", # Wind "WindField", "airports_within_radius", "altitude_msl_for_pixel_size", "altitude_to_flight_level", "analyze_swath_gaps_overlaps", "awp_profile_locations_for_flight_line", "awp_profile_locations_for_plan", "box_around_center_line", "box_around_center_terrain", "box_around_polygon", "box_around_polygon_terrain", "build_graph", "calculate_swath_widths", "check_airspace_conflicts", "check_airspace_proximity", "check_lband_radar_exclusions", "classify_severity", "clear_airspace_cache", "compute_concentric_isochrones", # Flight planning "compute_flight_plan", "compute_isochrone", "compute_multi_base_isochrone", "compute_multi_refuel_isochrone", "compute_refuel_isochrone", "convert_agl_floors", "convert_angle", "convert_distance", "convert_speed", "convert_time", "coordinated_line", "create_sensor", "eMAS", "evaluate_target_reachability", "fetch_and_check", "filter_by_schedule", "find_nearest_airport", "find_nearest_airports", # AWP profiling "flag_awp_stable_segments", "flag_below_min_safe_speed", "flight_lines_to_waypoint_path", # Swath "generate_swath_polygon", "greedy_optimize", "initialize_data", "isochrone_polygon", "map_airspace", # Plotting "map_flight_lines", "map_isochrone", "plot_airspace_map", "plot_altitude_trajectory", "plot_conflict_matrix", "plot_flight_plan", "plot_isochrone", "plot_isochrone_static", "plot_oceanic_tracks", "plot_vertical_profile", "polygon", # Flight patterns "racetrack", "register_sensor", "releases_along_flight_line", "rosette", "sawtooth", # Logging "setup_logging", "simulate_descent_trajectory", "simulate_release", "solve_release_for_target", "spiral", "summarize_airspaces", "summarize_trajectories", "terminal_velocity_nrd41", "terminal_velocity_sippican_axctd", "terrain_profile_along_track", "to_er2_csv", # Exports "to_excel", "to_foreflight_csv", "to_gpx", "to_honeywell_fms", "to_icartt", "to_kml", "to_pilot_excel", "to_trackair", "to_txt", # Units "ureg", "wind_field_from_plan", ]