Source code for ephemeris_tools.params_env

"""Build EphemerisParams, ViewerParams, TrackerParams from CGI-style env vars."""

from __future__ import annotations

import logging

from ephemeris_tools.constants import DEFAULT_INTERVAL
from ephemeris_tools.params import (
    EphemerisParams,
    ExtraStar,
    Observer,
    TrackerParams,
    ViewerCenter,
    ViewerDisplayInfo,
    ViewerParams,
    _get_env,
    _get_keys_env,
    _is_ra_hours_from_raw,
    _normalize_time_unit,
    _parse_observatory_coords,
    _parse_sexagesimal_to_degrees,
    _safe_float,
    parse_column_spec,
    parse_mooncol_spec,
)
from ephemeris_tools.planets import parse_moon_spec

logger = logging.getLogger(__name__)


[docs] def ephemeris_params_from_env() -> EphemerisParams | None: """Build EphemerisParams from CGI-style environment variables. Reads NPLANET, start, stop, interval, viewpoint, columns, etc. from env. Returns: EphemerisParams or None if required keys are missing/invalid. """ nplanet_s = _get_env('NPLANET') if len(nplanet_s) == 0: return None try: nplanet = int(nplanet_s.strip()) except ValueError as e: logger.error('Invalid NPLANET %r (must be integer 4-9): %s', nplanet_s, e) return None if nplanet < 4 or nplanet > 9: logger.error('NPLANET %d out of range (must be 4-9)', nplanet) return None start = _get_env('start') or _get_env('START_TIME') stop = _get_env('stop') or _get_env('STOP_TIME') if len(start) == 0 or len(stop) == 0: return None interval_s = _get_env('interval', '1') try: interval = float(interval_s) except ValueError as e: logger.error('Invalid interval %r (must be number): %s; using 1.0', interval_s, e) interval = DEFAULT_INTERVAL time_unit = _normalize_time_unit(_get_env('time_unit', 'hour')) ephem_s = _get_env('ephem', '0') try: ephem_version = int(ephem_s.split()[0]) except (ValueError, IndexError) as e: logger.error('Invalid ephem %r (must be integer): %s; using 0 (latest)', ephem_s, e) ephem_version = 0 viewpoint = _get_env('viewpoint', 'observatory') observatory = _get_env('observatory', "Earth's center") if observatory.strip().lower() == "earth's center": observatory = "Earth's center" lat_s = _get_env('latitude') lon_s = _get_env('longitude') alt_s = _get_env('altitude') lon_dir = _get_env('lon_dir', 'east') try: lat = float(lat_s) if lat_s else None except ValueError: logger.error('Invalid latitude %r: must be numeric', lat_s) lat = None try: lon = float(lon_s) if lon_s else None except ValueError: logger.error('Invalid longitude %r: must be numeric', lon_s) lon = None try: alt = float(alt_s) if alt_s else None except ValueError: logger.error('Invalid altitude %r: must be numeric', alt_s) alt = None if lon is not None and lon_dir.strip().lower() == 'west': lon = -lon sc_traj_s = _get_env('sc_trajectory', '0') try: sc_trajectory = int(sc_traj_s[:4] or '0') except ValueError as e: logger.error('Invalid sc_trajectory %r: %s; using 0', sc_traj_s, e) sc_trajectory = 0 column_strs = _get_keys_env('columns') columns = parse_column_spec(column_strs) if column_strs else [] mooncol_strs = _get_keys_env('mooncols') mooncols = parse_mooncol_spec(mooncol_strs) if mooncol_strs else [] moon_strs = _get_keys_env('moons') moon_parsed = parse_moon_spec(nplanet, moon_strs) if moon_strs else [] moon_ids = [v if v >= 100 else 100 * nplanet + v for v in moon_parsed] return EphemerisParams( planet_num=nplanet, start_time=start, stop_time=stop, interval=interval, time_unit=time_unit, ephem_version=ephem_version, viewpoint=viewpoint, observatory=observatory, latitude_deg=lat, longitude_deg=lon, lon_dir=lon_dir, altitude_m=alt, sc_trajectory=sc_trajectory, # In CGI mode we preserve exactly what the form submitted, so empty # selections stay empty instead of injecting CLI defaults. columns=columns, mooncols=mooncols, moon_ids=moon_ids, ephem_display=_get_env('ephem') or None, columns_display=_get_keys_env('columns') or None, mooncols_display=_get_keys_env('mooncols') or None, moons_display=_get_keys_env('moons') or None, )
[docs] def viewer_params_from_env() -> ViewerParams | None: """Build ``ViewerParams`` from CGI-style environment variables.""" nplanet_s = _get_env('NPLANET') if len(nplanet_s) == 0: return None try: planet_num = int(nplanet_s) except ValueError: return None if planet_num < 4 or planet_num > 9: logger.error('NPLANET %d out of range (must be 4-9)', planet_num) return None time_str = _get_env('time') if len(time_str) == 0: return None fov_s = _get_env('fov', '1') try: fov_value = float(fov_s) except ValueError: # FORTRAN list-directed READ treats comma as a value separator, so # strings like "557,000" parse as 557. Emulate that behavior. head = fov_s.split(',', 1)[0].strip() try: fov_value = float(head) except ValueError: fov_value = 1.0 fov_unit = _get_env('fov_unit', 'degrees') center_mode = _get_env('center', 'body') if center_mode == 'J2000': ra_type_raw = _get_env('center_ra_type', 'hours') or 'hours' is_ra_hours = _is_ra_hours_from_raw(ra_type_raw) try: ra_deg = _parse_sexagesimal_to_degrees( _get_env('center_ra', '0'), is_ra_hours=is_ra_hours, ) except ValueError: ra_deg = 0.0 try: dec_deg = _parse_sexagesimal_to_degrees( _get_env('center_dec', '0'), is_ra_hours=False, ) except ValueError: dec_deg = 0.0 center = ViewerCenter(mode='J2000', ra_deg=ra_deg, dec_deg=dec_deg) elif center_mode == 'ansa': center = ViewerCenter( mode='ansa', ansa_name=_get_env('center_ansa') or None, ansa_ew=_get_env('center_ew', 'east'), ) elif center_mode == 'star': center = ViewerCenter(mode='star', star_name=_get_env('center_star') or None) else: center = ViewerCenter(mode='body', body_name=_get_env('center_body') or None) viewpoint = _get_env('viewpoint', 'observatory') observer = Observer(name="Earth's center") viewpoint_display: str | None = None if viewpoint == 'latlon': lat_s = _get_env('latitude') lon_s = _get_env('longitude') alt_s = _get_env('altitude') lon_dir = _get_env('lon_dir', 'east') try: lat = float(lat_s) if lat_s else None except ValueError: lat = None try: lon = float(lon_s) if lon_s else None except ValueError: lon = None if lon is not None and lon_dir.strip().lower() == 'west': lon = -lon try: alt = float(alt_s) if alt_s else None except ValueError: alt = None observer = Observer(latitude_deg=lat, longitude_deg=lon, lon_dir=lon_dir, altitude_m=alt) if lat_s and lon_s and alt_s: # FORTRAN captions preserve original CGI precision for lat/lon/alt text. viewpoint_display = f'({lat_s}, {lon_s} {lon_dir}, {alt_s})' elif viewpoint == 'observatory': obs_name = _get_env('observatory', "Earth's center") if obs_name.strip().lower() == "earth's center": obs_name = "Earth's center" coords = _parse_observatory_coords(obs_name) if coords is None: observer = Observer(name=obs_name) else: lat, lon, alt = coords observer = Observer( name=obs_name, latitude_deg=lat, longitude_deg=lon, altitude_m=alt, ) elif viewpoint: observer = Observer(name=viewpoint) moon_tokens = _get_keys_env('moons') moon_ids = parse_moon_spec(planet_num, moon_tokens) if moon_tokens else None moremoons = (_get_env('moremoons') or '').strip().lower() in {'yes', 'y', 'true', '1'} rings_raw = _get_env('rings') ring_names = None if rings_raw: ring_names = [] for comma_part in rings_raw.split(','): for amp_part in comma_part.split('&'): token = amp_part.strip() if token: ring_names.append(token) blank_flag = _get_env('blank', '').lower() blank_disks = blank_flag in {'yes', 'y', 'true', '1'} meridians_flag = _get_env('meridians', '').lower() meridians = meridians_flag in {'yes', 'y', 'true', '1'} opacity = _get_env('opacity', 'Transparent') or 'Transparent' peris = _get_env('peris', 'None') or 'None' peripts_s = _get_env('peripts', '4') try: peripts = float(peripts_s) except ValueError: peripts = 4.0 arcmodel = _get_env('arcmodel') or None arcpts_s = _get_env('arcpts', '4') try: arcpts = float(arcpts_s) except ValueError: arcpts = 4.0 other_bodies = _get_keys_env('other') labels = ( _get_env('labels') or _get_env('Labels') or 'Small (6 points)' ).strip() or 'Small (6 points)' moonpts_s = _get_env('moonpts', '0') try: moonpts = float(moonpts_s) except ValueError: moonpts = 0.0 title = _get_env('title') standard_flag = _get_env('standard', '').lower() show_standard_stars = standard_flag in {'yes', 'y', 'true', '1'} additional_flag = _get_env('additional', '').lower() extra_star: ExtraStar | None = None if additional_flag in {'yes', 'y', 'true', '1'}: extra_ra_s = _get_env('extra_ra', '') extra_dec_s = _get_env('extra_dec', '') extra_ra_type_raw = _get_env('extra_ra_type', 'hours') or 'hours' is_extra_ra_hours = _is_ra_hours_from_raw(extra_ra_type_raw) if extra_ra_s.strip() and extra_dec_s.strip(): try: extra_star = ExtraStar( name=_get_env('extra_name', ''), ra_deg=_parse_sexagesimal_to_degrees( extra_ra_s, is_ra_hours=is_extra_ra_hours, ), dec_deg=_parse_sexagesimal_to_degrees( extra_dec_s, is_ra_hours=False, ), ) except ValueError: extra_star = None ephem_s = _get_env('ephem', '0') parts = (ephem_s or '').strip().split() ephem_value = parts[0] if parts else '0' try: ephem_version = int(ephem_value) except (ValueError, IndexError): ephem_version = 0 display = ViewerDisplayInfo( ephem_display=_get_env('ephem') or None, moons_display=_get_env('moons') or None, rings_display=_get_env('rings') or None, viewpoint_display=viewpoint_display, center_ra_display=_get_env('center_ra'), center_ra_type_display=_get_env('center_ra_type'), center_dec_display=_get_env('center_dec'), moonpts_display=_get_env('moonpts'), blank_display=_get_env('blank'), meridians_display=_get_env('meridians'), additional_display=_get_env('additional'), extra_name_display=_get_env('extra_name'), extra_ra_display=_get_env('extra_ra'), extra_ra_type_display=_get_env('extra_ra_type'), extra_dec_display=_get_env('extra_dec'), ) return ViewerParams( planet_num=planet_num, time_str=time_str, fov_value=fov_value, fov_unit=fov_unit, center=center, observer=observer, ephem_version=ephem_version, moon_ids=moon_ids, ring_names=ring_names, blank_disks=blank_disks, opacity=opacity, labels=labels, moonpts=moonpts, peris=peris, peripts=peripts, meridians=meridians, arcmodel=arcmodel, arcpts=arcpts, torus=_get_env('torus', '').strip().lower() in {'yes', 'y', 'true', '1'}, torus_inc=_safe_float(_get_env('torus_inc', '6.8') or '6.8', 6.8), torus_rad=_safe_float(_get_env('torus_rad', '422000') or '422000', 422000), moremoons=moremoons, other_bodies=other_bodies if other_bodies else None, show_standard_stars=show_standard_stars, extra_star=extra_star, title=title, display=display, )
[docs] def tracker_params_from_env() -> TrackerParams | None: """Build ``TrackerParams`` from CGI-style environment variables.""" nplanet_s = _get_env('NPLANET') if len(nplanet_s) == 0: return None try: planet_num = int(nplanet_s) except ValueError: return None if planet_num < 4 or planet_num > 9: logger.error('NPLANET %d out of range (must be 4-9)', planet_num) return None start_time = _get_env('start') stop_time = _get_env('stop') if len(start_time) == 0 or len(stop_time) == 0: return None interval_s = _get_env('interval', '1') try: interval = float(interval_s) except ValueError: interval = DEFAULT_INTERVAL time_unit = _normalize_time_unit(_get_env('time_unit', 'hour')) viewpoint = _get_env('viewpoint', 'observatory') observer = Observer(name="Earth's center") if viewpoint == 'observatory': obs_name = _get_env('observatory', "Earth's center") if obs_name.strip().lower() == "earth's center": obs_name = "Earth's center" coords = _parse_observatory_coords(obs_name) if coords is None: observer = Observer(name=obs_name) else: lat, lon, alt = coords observer = Observer( name=obs_name, latitude_deg=lat, longitude_deg=lon, altitude_m=alt, ) elif viewpoint == 'latlon': lat_s = _get_env('latitude') lon_s = _get_env('longitude') alt_s = _get_env('altitude') lat_deg: float | None lon_deg: float | None alt_m: float | None try: lat_deg = float(lat_s) if lat_s else None except ValueError: lat_deg = None try: lon_deg = float(lon_s) if lon_s else None except ValueError: lon_deg = None try: alt_m = float(alt_s) if alt_s else None except ValueError: alt_m = None lon_dir = _get_env('lon_dir', 'east') if lon_deg is not None and lon_dir.strip().lower() == 'west': lon_deg = -lon_deg observer = Observer( latitude_deg=lat_deg, longitude_deg=lon_deg, altitude_m=alt_m, lon_dir=lon_dir, ) elif viewpoint: observer = Observer(name=viewpoint) moon_tokens = _get_keys_env('moons') moon_ids = parse_moon_spec(planet_num, moon_tokens) if moon_tokens else [] rings_raw = _get_keys_env('rings') ring_names = [r.strip() for r in rings_raw if r.strip()] if rings_raw else None xrange_s = _get_env('xrange') try: xrange = float(xrange_s) if xrange_s else None except ValueError: xrange = None xunit_raw = _get_env('xunit', 'arcsec') # Preserve raw value to match FORTRAN display (e.g. "degrees", "Uranus radii") xunit = (xunit_raw or 'arcsec').strip() or 'arcsec' title = _get_env('title') ephem_s = _get_env('ephem', '0') parts = (ephem_s or '').strip().split() ephem_value = parts[0] if parts else '0' try: ephem_version = int(ephem_value) except (ValueError, IndexError): ephem_version = 0 sc_traj_s = _get_env('sc_trajectory', '0') try: sc_trajectory = int(sc_traj_s[:4] or '0') except ValueError: sc_trajectory = 0 ephem_display = _get_env('ephem') or None moons_display = _get_keys_env('moons') or None rings_display = _get_keys_env('rings') or None viewpoint_display: str | None = None if viewpoint == 'latlon': lat_s = _get_env('latitude') lon_s = _get_env('longitude') lon_dir = _get_env('lon_dir', 'east') alt_s = _get_env('altitude') if lat_s and lon_s and alt_s: # Match FORTRAN: '(' // lat // ',' // ' ' // lon // ' ' // lon_dir // ',' // alt // ')' viewpoint_display = f'({lat_s}, {lon_s} {lon_dir}, {alt_s})' return TrackerParams( planet_num=planet_num, start_time=start_time, stop_time=stop_time, interval=interval, time_unit=time_unit, observer=observer, ephem_version=ephem_version, sc_trajectory=sc_trajectory, moon_ids=moon_ids, ring_names=ring_names, xrange=xrange, xunit=xunit, title=title, ephem_display=ephem_display, moons_display=moons_display, rings_display=rings_display, viewpoint_display=viewpoint_display, )