Source code for ephemeris_tools.cli.main

"""CLI entry point: ephemeris-tools ephemeris|tracker|viewer subcommands."""

from __future__ import annotations

import argparse
import contextlib
import logging
import os
import sys
from typing import NoReturn, TextIO, cast

from ephemeris_tools.constants import DEFAULT_INTERVAL, DEGREES_PER_HOUR_RA
from ephemeris_tools.ephemeris import generate_ephemeris
from ephemeris_tools.input_params import (
    write_input_parameters_ephemeris,
    write_input_parameters_tracker,
    write_input_parameters_viewer,
)
from ephemeris_tools.params import (
    EphemerisParams,
    ExtraStar,
    Observer,
    TrackerParams,
    ViewerParams,
    _is_ra_hours_from_raw,
    _parse_sexagesimal_to_degrees,
    ephemeris_params_from_env,
    parse_center,
    parse_column_spec,
    parse_fov,
    parse_mooncol_spec,
    parse_observer,
    parse_planet,
    tracker_params_from_env,
    viewer_params_from_env,
)
from ephemeris_tools.planets import parse_moon_spec
from ephemeris_tools.tracker import run_tracker
from ephemeris_tools.viewer import run_viewer

logger = logging.getLogger(__name__)


def _configure_logging(verbose: bool = False) -> None:
    """Configure logging for the CLI.

    Log output goes to stderr. Level is taken from --verbose or
    EPHEMERIS_TOOLS_LOG_LEVEL.

    Parameters:
        verbose: If True, set level to DEBUG; otherwise WARNING (unless
            overridden by environment).

    Returns:
        None.

    Raises:
        None.
    """
    level = logging.DEBUG if verbose else logging.WARNING
    env_level = os.environ.get('EPHEMERIS_TOOLS_LOG_LEVEL', '').upper()
    if env_level in ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'):
        level = getattr(logging, env_level)
    logging.basicConfig(
        level=level,
        format='%(levelname)s: %(name)s: %(message)s',
        stream=sys.stderr,
    )
    # Suppress noisy third-party DEBUG (OpenTelemetry, google.cloud.storage, etc.).
    for name in (
        'google',
        'google.cloud',
        'google.cloud.storage',
        'opentelemetry',
        'opentelemetry.sdk',
    ):
        logging.getLogger(name).setLevel(logging.WARNING)


def _default_center_ansa_for_planet(planet_num: int) -> str:
    """Return default ring ansa name when --center ansa is used without --center-ansa.

    Parameters:
        planet_num: Planet number (4=Mars, 5=Jupiter, 6=Saturn, 7=Uranus, 8=Neptune,
            9=Pluto). Used to look up a default ansa/ring name per planet.

    Returns:
        Default ansa name for the given planet. If planet_num is not in the mapping
        (e.g. invalid or unsupported), returns 'A Ring' as the fallback.
    """
    defaults: dict[int, str] = {
        4: 'Phobos Ring',
        5: 'Main Ring',
        6: 'A Ring',
        7: 'Epsilon Ring',
        8: 'Adams Ring',
        9: 'Charon',
    }
    return defaults.get(planet_num, 'A Ring')


def _ephemeris_cmd(parser: argparse.ArgumentParser, args: argparse.Namespace) -> int:
    """Run ephemeris generator (ephemeris subcommand).

    Parameters:
        parser: Argument parser (unused).
        args: Parsed args or CGI env; planet, start, stop, columns, etc.

    Returns:
        Exit code 0 on success, 1 on error.
    """
    if args.cgi:
        params = ephemeris_params_from_env()
        if params is None:
            print('Invalid or missing CGI parameters (e.g. NPLANET, start, stop).', file=sys.stderr)
            return 1
        if not args.output:
            args.output = os.environ.get('EPHEM_FILE', None)
    else:
        start = (args.start or '').strip()
        stop = (args.stop or '').strip()
        if not start or not stop:
            parser.error('--start and --stop are required when not using --cgi.')
        args.start = start
        args.stop = stop
        moons_raw = args.moons or []
        moon_ids = parse_moon_spec(args.planet, [str(x) for x in moons_raw])
        observer = Observer(name="Earth's center")
        viewpoint = (args.viewpoint or 'observatory').strip() or 'observatory'
        observatory = (args.observatory or "Earth's center").strip() or "Earth's center"

        if args.observer is not None:
            observer = parse_observer(args.observer)
            if observer.latitude_deg is not None and observer.longitude_deg is not None:
                viewpoint = 'latlon'
                observatory = "Earth's center"
            else:
                viewpoint = 'observatory'
                observatory = (observer.name or "Earth's center").strip() or "Earth's center"
        else:
            if viewpoint == 'latlon' and (args.latitude is not None or args.longitude is not None):
                pass
            else:
                observer = parse_observer([observatory])

        if viewpoint == 'latlon':
            if args.observer is not None and observer.latitude_deg is not None:
                lon_deg = observer.longitude_deg
                if lon_deg is not None and (observer.lon_dir or 'east').strip().lower() == 'west':
                    lon_deg = -lon_deg
                lat_deg = observer.latitude_deg
                lon_out = lon_deg
                alt_out = observer.altitude_m
                lon_dir_out = (observer.lon_dir or 'east').strip() or 'east'
            else:
                lat_deg = args.latitude
                lon_out = args.longitude
                if lon_out is not None and args.lon_dir == 'west':
                    lon_out = -lon_out
                alt_out = args.altitude
                lon_dir_out = args.lon_dir
        else:
            lat_deg = None
            lon_out = None
            alt_out = None
            lon_dir_out = args.lon_dir

        params = EphemerisParams(
            planet_num=args.planet,
            start_time=args.start,
            stop_time=args.stop,
            interval=args.interval,
            time_unit=args.time_unit,
            ephem_version=args.ephem,
            viewpoint=viewpoint,
            observatory=observatory,
            latitude_deg=lat_deg if viewpoint == 'latlon' else None,
            longitude_deg=lon_out if viewpoint == 'latlon' else None,
            lon_dir=lon_dir_out,
            altitude_m=alt_out if viewpoint == 'latlon' else None,
            sc_trajectory=args.sc_trajectory,
            columns=parse_column_spec([str(x) for x in (args.columns or [])]) or [1, 2, 3, 15, 8],
            mooncols=parse_mooncol_spec([str(x) for x in (args.mooncols or [])]) or [5, 6, 8, 9],
            moon_ids=moon_ids,
        )

    write_input_parameters_ephemeris(sys.stdout, params)

    out: TextIO = sys.stdout
    if args.output is not None:
        try:
            with open(args.output, 'w') as f:
                generate_ephemeris(params, f)
            return 0
        except (ValueError, RuntimeError) as e:
            print(f'Error: {e}', file=sys.stderr)
            return 1
    try:
        generate_ephemeris(params, out)
        return 0
    except (ValueError, RuntimeError) as e:
        print(f'Error: {e}', file=sys.stderr)
        return 1


[docs] def main() -> int: """Entry point for ephemeris-tools CLI (ephemeris | tracker | viewer). Returns: Exit code 0 on success, 1 on failure. """ parser = argparse.ArgumentParser( prog='ephemeris-tools', description='Planetary ephemeris, moon tracker, and planet viewer.', ) subparsers = parser.add_subparsers(dest='command', required=True) ephem_parser = subparsers.add_parser('ephemeris', help='Generate ephemeris table') ephem_parser.add_argument( '--cgi', action='store_true', help='Read parameters from environment (CGI)' ) ephem_parser.add_argument( '--planet', type=parse_planet, default=6, help='Planet number or name (4=mars..9=pluto); env: NPLANET', ) ephem_parser.add_argument( '--start', type=str, required=False, help='Start time; env: start, START_TIME' ) ephem_parser.add_argument( '--stop', type=str, required=False, help='Stop time; env: stop, STOP_TIME' ) ephem_parser.add_argument( '--interval', type=float, default=DEFAULT_INTERVAL, help='Time step; env: interval' ) ephem_parser.add_argument( '--time-unit', type=str, default='hour', choices=['sec', 'min', 'hour', 'day'], help='env: time_unit', ) ephem_parser.add_argument( '--ephem', type=int, default=0, help='Ephemeris version (0=latest); env: ephem' ) ephem_parser.add_argument( '--viewpoint', type=str, default='', help="observatory, latlon, or Earth's center; env: viewpoint", ) ephem_parser.add_argument( '--observer', type=str, nargs='+', default=None, help='Observer shortcut: name or "lat lon alt"', ) ephem_parser.add_argument( '--observatory', type=str, default='', help='Observatory name when viewpoint=observatory; env: observatory', ) ephem_parser.add_argument( '--latitude', type=float, default=None, help='Latitude (deg) when viewpoint=latlon; env: latitude', ) ephem_parser.add_argument( '--longitude', type=float, default=None, help='Longitude (deg); env: longitude' ) ephem_parser.add_argument( '--lon-dir', type=str, default='east', choices=['east', 'west'], help='env: lon_dir' ) ephem_parser.add_argument( '--altitude', type=float, default=None, help='Altitude (m) when viewpoint=latlon; env: altitude', ) ephem_parser.add_argument( '--sc-trajectory', type=int, default=0, help='Spacecraft trajectory version; env: sc_trajectory', ) ephem_parser.add_argument( '--columns', type=str, nargs='*', default=None, help='Column IDs or names (e.g. 1 2 ymdhms radec); env: columns', ) ephem_parser.add_argument( '--mooncols', type=str, nargs='*', default=None, help='Moon column IDs or names (e.g. 5 6 radec offset); env: mooncols', ) ephem_parser.add_argument( '--moons', type=str, nargs='*', default=None, help='Moon indices or names (e.g. 1 2 io europa); env: moons', ) ephem_parser.add_argument( '-o', '--output', type=str, default=None, help='Output file; env: EPHEM_FILE' ) ephem_parser.add_argument('-v', '--verbose', action='store_true', help='Show INFO logs') ephem_parser.set_defaults(func=_ephemeris_cmd) track_parser = subparsers.add_parser('tracker', help='Moon tracker plot') track_parser.add_argument( '--cgi', action='store_true', help='Read parameters from environment (CGI)' ) track_parser.add_argument( '--planet', type=parse_planet, default=6, help='Planet number or name; env: NPLANET' ) track_parser.add_argument('--start', type=str, required=False, help='Start time; env: start') track_parser.add_argument('--stop', type=str, required=False, help='Stop time; env: stop') track_parser.add_argument( '--interval', type=float, default=DEFAULT_INTERVAL, help='Time step; env: interval' ) track_parser.add_argument( '--time-unit', type=str, default='hour', choices=['sec', 'min', 'hour', 'day'], help='env: time_unit', ) track_parser.add_argument( '--ephem', type=int, default=0, help='Ephemeris version (0=latest); env: ephem' ) track_parser.add_argument( '--observer', type=str, nargs='+', default=None, help='Observer shortcut: name or "lat lon alt"', ) track_parser.add_argument( '--viewpoint', type=str, default='', help="observatory, latlon, or Earth's center; env: viewpoint", ) track_parser.add_argument( '--observatory', type=str, default='', help='Observatory when viewpoint=observatory; env: observatory', ) track_parser.add_argument( '--latitude', type=float, default=None, help='Latitude (deg); env: latitude' ) track_parser.add_argument( '--longitude', type=float, default=None, help='Longitude (deg); env: longitude' ) track_parser.add_argument( '--lon-dir', type=str, default='east', choices=['east', 'west'], help='env: lon_dir' ) track_parser.add_argument( '--altitude', type=float, default=None, help='Altitude (m); env: altitude' ) track_parser.add_argument( '--sc-trajectory', type=int, default=0, help='Spacecraft trajectory; env: sc_trajectory' ) track_parser.add_argument( '--moons', type=str, nargs='*', default=None, help='Moon indices or names (e.g. 1 2 io europa); env: moons', ) track_parser.add_argument( '--rings', type=str, nargs='*', default=None, help='Ring option codes or names (e.g. 61 62, main ge); env: rings', ) track_parser.add_argument( '--xrange', type=float, default=None, help='Half-range of x-axis (arcsec or radii); env: xrange', ) track_parser.add_argument( '--xunit', type=str, default='arcsec', choices=['arcsec', 'radii'], help='x-axis units; env: xunit', ) track_parser.add_argument('--title', type=str, default='', help='Plot title; env: title') track_parser.add_argument( '-o', '--output', type=str, default=None, help='PostScript file; env: TRACKER_POSTFILE' ) track_parser.add_argument( '--output-txt', type=str, default=None, help='Text table file; env: TRACKER_TEXTFILE' ) track_parser.add_argument('-v', '--verbose', action='store_true', help='Show INFO logs') track_parser.set_defaults(func=_tracker_cmd) view_parser = subparsers.add_parser('viewer', help='Planet viewer diagram') view_parser.add_argument( '--cgi', action='store_true', help='Read parameters from environment (CGI)' ) view_parser.add_argument( '--planet', type=parse_planet, default=6, help='Planet number or name; env: NPLANET' ) view_parser.add_argument('--time', type=str, default='', help='Observation time; env: time') view_parser.add_argument( '--ephem', type=int, default=0, help='Ephemeris version (0=latest); env: ephem' ) view_parser.add_argument( '--fov', type=str, nargs='+', default=None, help='Field of view as value + unit tokens (e.g. 3 Neptune radii)', ) view_parser.add_argument( '--fov-unit', type=str, default='deg', help=( 'FOV unit: deg, arcmin, arcsec, or instrument name ' '(e.g. Voyager ISS narrow angle FOVs); env: fov_unit' ), ) view_parser.add_argument( '--center', type=str, nargs='+', default=None, help='Diagram center tokens (e.g. neptune, leverrier west, 12.5 -30.2)', ) view_parser.add_argument( '--center-body', type=str, default='', help='Body name when center=body; env: center_body' ) view_parser.add_argument( '--center-ansa', type=str, default='', help='Ring ansa when center=ansa; env: center_ansa' ) view_parser.add_argument( '--center-ew', type=str, default='east', help='East/west when center=ansa; env: center_ew' ) view_parser.add_argument( '--center-ra', type=float, default=0.0, help='Center RA (deg); env: center_ra' ) view_parser.add_argument( '--center-dec', type=float, default=0.0, help='Center Dec (deg); env: center_dec' ) view_parser.add_argument( '--center-ra-type', type=str, default='hours', help='RA units; env: center_ra_type' ) view_parser.add_argument( '--center-star', type=str, default='', help='Star name when center=star; env: center_star' ) view_parser.add_argument( '--viewpoint', type=str, default='', help="observatory, latlon, or Earth's center; env: viewpoint", ) view_parser.add_argument( '--observer', type=str, nargs='+', default=None, help='Observer shortcut: name or "lat lon alt"', ) view_parser.add_argument( '--observatory', type=str, default='', help='Observatory name; env: observatory' ) view_parser.add_argument( '--latitude', type=float, default=None, help='Latitude (deg); env: latitude' ) view_parser.add_argument( '--longitude', type=float, default=None, help='Longitude (deg); env: longitude' ) view_parser.add_argument( '--lon-dir', type=str, default='east', choices=['east', 'west'], help='env: lon_dir' ) view_parser.add_argument( '--altitude', type=float, default=None, help='Altitude (m); env: altitude' ) view_parser.add_argument( '--sc-trajectory', type=int, default=0, help='Spacecraft trajectory; env: sc_trajectory' ) view_parser.add_argument( '--moons', type=str, nargs='*', default=None, help='Moon indices or names (e.g. 1 2 io europa); env: moons', ) view_parser.add_argument( '--moremoons', action='store_true', help='Also show irregular moons (in addition to --moons selection); env: moremoons', ) view_parser.add_argument( '--rings', type=str, nargs='*', default=None, help='Ring option codes or names; env: rings' ) view_parser.add_argument( '--io-torus', action='store_true', help='Show Io plasma torus (Jupiter only); env: torus', ) view_parser.add_argument( '--io-torus-inc', type=float, default=6.8, help='Io torus inclination (deg); env: torus_inc' ) view_parser.add_argument( '--io-torus-rad', type=float, default=422000.0, help='Io torus radius (km); env: torus_rad' ) view_parser.add_argument( '--ephem-display', type=str, default=None, help='Ephemeris string for Input Parameters (e.g. NEP095 + DE440); env: ephem_display', ) view_parser.add_argument( '--moons-display', type=str, default=None, help=( 'Moon selection string for Input Parameters ' '(e.g. 802 Triton & Nereid); env: moons_display' ), ) view_parser.add_argument( '--rings-display', type=str, default=None, help=( 'Ring selection string for Input Parameters (e.g. LeVerrier, Arago); env: rings_display' ), ) view_parser.add_argument( '--standard-star-catalog', action='store_true', help='Overlay standard star catalog (from planet starlist); env: standard', ) view_parser.add_argument( '--additional-star', action='store_true', help='Overlay user star (provide --extra-ra and --extra-dec); env: additional', ) view_parser.add_argument( '--extra-name', type=str, default=None, help='Additional star name; env: extra_name' ) view_parser.add_argument( '--extra-ra', type=str, default=None, help='Additional star RA; env: extra_ra' ) view_parser.add_argument( '--extra-ra-type', type=str, default=None, help='Additional star RA type; env: extra_ra_type', ) view_parser.add_argument( '--extra-dec', type=str, default=None, help='Additional star Dec; env: extra_dec' ) view_parser.add_argument( '--other', type=str, nargs='*', default=None, help='Other bodies; env: other' ) view_parser.add_argument('--title', type=str, default='', help='Diagram title; env: title') view_parser.add_argument( '--labels', type=str, default=None, help='Moon label size: small, medium, or large; env: labels', ) view_parser.add_argument( '--moonpts', type=str, default=None, help='Moon enlargement (points); env: moonpts' ) view_parser.add_argument( '--blank-disks', action='store_true', help='Blank (white-out) planet and moon disks; env: blank', ) view_parser.add_argument( '--ring-opacity', type=str, default=None, help='Ring plot type: Transparent, Semi-transparent, or Opaque; env: opacity', ) view_parser.add_argument( '--ring-pericenter-markers', type=str, default=None, help='Pericenter markers (planet-specific); env: peris', ) view_parser.add_argument( '--ring-pericenter-size', type=str, default=None, help='Pericenter marker size in points; env: peripts', ) view_parser.add_argument( '--meridians', action='store_true', help='Show prime meridians; env: meridians', ) view_parser.add_argument( '--neptune-arc-model', type=str, default=None, help='Neptune arc motion model (#1, #2, or #3); env: arcmodel', ) view_parser.add_argument( '--neptune-arc-thickness', type=str, default=None, help='Neptune arc weight in points; env: arcpts', ) view_parser.add_argument( '-o', '--output', type=str, default=None, help='PostScript file; env: VIEWER_POSTFILE' ) view_parser.add_argument( '--output-txt', type=str, default=None, help='Field of View table file' ) view_parser.add_argument('-v', '--verbose', action='store_true', help='Show INFO logs') view_parser.set_defaults(func=_viewer_cmd) args = parser.parse_args() _configure_logging(verbose=args.verbose) return cast(int, args.func(parser, args))
def _tracker_cmd(parser: argparse.ArgumentParser, args: argparse.Namespace) -> int: """Run moon tracker (tracker subcommand). Parameters: parser: Argument parser (unused). args: Parsed args; planet, start, stop, moons, rings, output, etc. Returns: Exit code 0 on success, 1 on error. """ if args.cgi: params = tracker_params_from_env() if params is None: print('Invalid or missing CGI tracker parameters.', file=sys.stderr) return 1 write_input_parameters_tracker(sys.stdout, params) with contextlib.ExitStack() as stack: post_path = os.environ.get('TRACKER_POSTFILE') txt_path = os.environ.get('TRACKER_TEXTFILE') if post_path: params.output_ps = stack.enter_context(open(post_path, 'w')) if txt_path: params.output_txt = stack.enter_context(open(txt_path, 'w')) try: run_tracker(params) return 0 except (ValueError, RuntimeError) as e: print(f'Error: {e}', file=sys.stderr) return 1 start = (args.start or '').strip() stop = (args.stop or '').strip() if not start or not stop: parser.error('--start and --stop are required when not using --cgi.') args.start = start.strip() args.stop = stop.strip() moons_raw = args.moons or [] moon_ids = parse_moon_spec(args.planet, [str(x) for x in moons_raw]) if args.observer is not None: observer = parse_observer(args.observer) else: observer_name = (args.observatory or "Earth's center").strip() or "Earth's center" lat = args.latitude lon = args.longitude lon_dir = (args.lon_dir or 'east').strip().lower() alt = args.altitude if lat is not None or lon is not None or lon_dir != 'east' or alt is not None: lon_deg = lon if lon_deg is not None and lon_dir == 'west': lon_deg = -lon_deg observer = Observer( name=observer_name, latitude_deg=lat, longitude_deg=lon_deg, lon_dir=lon_dir, altitude_m=alt, ) else: observer = parse_observer([observer_name]) viewpoint_display: str | None = None if observer.latitude_deg is not None and observer.longitude_deg is not None: lon_deg = observer.longitude_deg lon_dir = 'west' if lon_deg < 0 else (observer.lon_dir or 'east').strip().lower() lon_display = abs(lon_deg) lat_s = str(observer.latitude_deg) lon_s = str(lon_display) alt_s = str(observer.altitude_m) if observer.altitude_m is not None else '0' viewpoint_display = f'({lat_s}, {lon_s} {lon_dir}, {alt_s})' tracker_params = TrackerParams( planet_num=args.planet, start_time=args.start, stop_time=args.stop, interval=args.interval, time_unit=args.time_unit, observer=observer, ephem_version=args.ephem, sc_trajectory=args.sc_trajectory, moon_ids=moon_ids, ring_names=(args.rings or None), xrange=args.xrange, xunit=args.xunit, title=(args.title or '').strip(), viewpoint_display=viewpoint_display, ) write_input_parameters_tracker(sys.stdout, tracker_params) with contextlib.ExitStack() as stack: out_ps = stack.enter_context(open(args.output, 'w')) if args.output is not None else None out_txt = ( stack.enter_context(open(args.output_txt, 'w')) if args.output_txt is not None else None ) try: tracker_params.output_ps = out_ps tracker_params.output_txt = out_txt run_tracker(tracker_params) except (ValueError, RuntimeError) as e: print(f'Error: {e}', file=sys.stderr) return 1 return 0 def _viewer_cmd(parser: argparse.ArgumentParser, args: argparse.Namespace) -> int: """Run planet viewer (viewer subcommand). Parameters: parser: Argument parser (unused). args: Parsed args; planet, time, fov, center, output, etc. Returns: Exit code 0 on success, 1 on error. """ if args.cgi: params = viewer_params_from_env() if params is None: print('Invalid or missing CGI viewer parameters.', file=sys.stderr) return 1 write_input_parameters_viewer(sys.stdout, params) with contextlib.ExitStack() as stack: post_path = os.environ.get('VIEWER_POSTFILE') txt_path = os.environ.get('VIEWER_TEXTFILE') if post_path: params.output_ps = stack.enter_context(open(post_path, 'w')) if txt_path: params.output_txt = stack.enter_context(open(txt_path, 'w')) try: run_viewer(params) return 0 except (ValueError, RuntimeError) as e: print(f'Error: {e}', file=sys.stderr) return 1 with contextlib.ExitStack() as stack: out = stack.enter_context(open(args.output, 'w')) if args.output is not None else None out_txt = ( stack.enter_context(open(args.output_txt, 'w')) if args.output_txt is not None else None ) fov_tokens = args.fov if fov_tokens: if len(fov_tokens) == 1 and args.fov_unit: fov_tokens = [fov_tokens[0], args.fov_unit] fov_value, fov_unit = parse_fov(fov_tokens) else: fov_value, fov_unit = (1.0, 'degrees') if args.observer is not None: observer = parse_observer(args.observer) elif (args.viewpoint or '').strip().lower() == 'latlon': lon_dir = (args.lon_dir or 'east').strip().lower() lat = args.latitude lon = args.longitude alt = args.altitude if lon is not None and lon_dir == 'west': lon = -lon observer = Observer( latitude_deg=lat, longitude_deg=lon, lon_dir=lon_dir, altitude_m=alt, ) else: observer_name = (args.observatory or "Earth's center").strip() or "Earth's center" observer = parse_observer([observer_name]) center_tokens = args.center if center_tokens: first = center_tokens[0].lower() if first == 'body' and args.center_body is not None: center = parse_center(args.planet, [str(args.center_body)]) elif first == 'j2000': is_ra_hours = _is_ra_hours_from_raw(args.center_ra_type) ra_deg = ( float(args.center_ra) * DEGREES_PER_HOUR_RA if is_ra_hours else float(args.center_ra) ) center = parse_center( args.planet, [str(ra_deg), str(args.center_dec)], ) elif first == 'ansa': ansa_name = (args.center_ansa or '').strip() if not ansa_name: ansa_name = _default_center_ansa_for_planet(args.planet) center = parse_center( args.planet, [ansa_name, str(args.center_ew)], ) elif first == 'star': center = parse_center(args.planet, [str(args.center_star)]) else: center = parse_center(args.planet, [str(x) for x in center_tokens]) else: center = parse_center(args.planet, []) moon_ids = None if args.moons: moon_ids = parse_moon_spec(args.planet, [str(x) for x in args.moons]) rings_raw = [str(r) for r in (args.rings or [])] ring_names: list[str] | None = None if rings_raw: ring_names = [] for token in rings_raw: for comma_part in token.split(','): for amp_part in comma_part.split('&'): part = amp_part.strip() if part: ring_names.append(part) opacity = (args.ring_opacity or 'Transparent').strip() or 'Transparent' peris = (args.ring_pericenter_markers or 'None').strip() or 'None' peripts_raw = (args.ring_pericenter_size or '4').strip() try: peripts = float(peripts_raw) except ValueError: peripts = 4.0 arcmodel = (args.neptune_arc_model or '').strip() or None arcpts_raw = (args.neptune_arc_thickness or '4').strip() try: arcpts = float(arcpts_raw) except ValueError: arcpts = 4.0 labels = (args.labels or 'Small (6 points)').strip() moonpts_raw = (args.moonpts or '0').strip() try: moonpts = float(moonpts_raw) except ValueError: moonpts = 0.0 show_standard_stars = args.standard_star_catalog extra_star: ExtraStar | None = None extra_ra = (args.extra_ra or '').strip() extra_dec = (args.extra_dec or '').strip() if args.additional_star and extra_ra and extra_dec: is_hours = _is_ra_hours_from_raw(args.extra_ra_type or 'hours') try: extra_star = ExtraStar( name=(args.extra_name or '').strip(), ra_deg=_parse_sexagesimal_to_degrees(extra_ra, is_ra_hours=is_hours), dec_deg=_parse_sexagesimal_to_degrees(extra_dec, is_ra_hours=False), ) except ValueError as e: logger.warning( 'Invalid extra star input (extra_name=%r, extra_ra=%r, extra_dec=%r, ' 'is_hours=%s): %s', args.extra_name, extra_ra, extra_dec, is_hours, e, ) extra_star = None viewer_params = ViewerParams( planet_num=args.planet, time_str=args.time, fov_value=fov_value, fov_unit=fov_unit, center=center, observer=observer, ephem_version=args.ephem, moon_ids=moon_ids, ring_names=ring_names, blank_disks=args.blank_disks, opacity=opacity, labels=labels, moonpts=moonpts, peris=peris, peripts=peripts, meridians=args.meridians, arcmodel=arcmodel, arcpts=arcpts, torus=args.io_torus, torus_inc=float(args.io_torus_inc), torus_rad=float(args.io_torus_rad), show_standard_stars=show_standard_stars, extra_star=extra_star, other_bodies=[str(o) for o in (args.other or [])] or None, title=(args.title or '').strip(), moremoons=args.moremoons, output_ps=out, output_txt=out_txt, ) write_input_parameters_viewer(sys.stdout, viewer_params) try: run_viewer(viewer_params) except (ValueError, RuntimeError) as e: print(f'Error: {e}', file=sys.stderr) return 1 return 0
[docs] def cli_main() -> NoReturn: """Entry point for console_scripts; calls main() and exits with its return code.""" sys.exit(main())
if __name__ == '__main__': sys.exit(main())