Source code for ephemeris_tools.planets

"""Planet-specific configurations (moons, rings, orbital elements)."""

import logging

from ephemeris_tools.planets.base import ArcSpec, MoonSpec, PlanetConfig, RingSpec
from ephemeris_tools.planets.jupiter import JUPITER_CONFIG
from ephemeris_tools.planets.mars import MARS_CONFIG
from ephemeris_tools.planets.neptune import NEPTUNE_CONFIG
from ephemeris_tools.planets.pluto import PLUTO_CONFIG
from ephemeris_tools.planets.saturn import SATURN_CONFIG
from ephemeris_tools.planets.uranus import URANUS_CONFIG

logger = logging.getLogger(__name__)

_PLANET_CONFIGS: dict[int, PlanetConfig] = {
    4: MARS_CONFIG,
    5: JUPITER_CONFIG,
    6: SATURN_CONFIG,
    7: URANUS_CONFIG,
    8: NEPTUNE_CONFIG,
    9: PLUTO_CONFIG,
}


[docs] def get_moon_name_to_index(planet_num: int) -> dict[str, int]: """Return mapping of lowercase moon name to list index for the planet. Parameters: planet_num: Planet number (4-9). Returns: Dict mapping moon name (lowercase) to index in moons list (planet center excluded from count). """ cfg = _PLANET_CONFIGS.get(planet_num) if cfg is None: return {} return {moon.name.lower(): i for i, moon in enumerate(cfg.moons) if moon.id != cfg.planet_id}
[docs] def parse_moon_spec(planet_num: int, tokens: list[str]) -> list[int]: """Convert moon tokens to NAIF moon IDs (CLI/CGI moon selection). Parameters: planet_num: Planet number (4-9). tokens: List of 1-based indices, NAIF IDs, or case-insensitive names. Returns: List of NAIF moon IDs. Supports ``classical`` and ``all`` group keywords. Unknown names are skipped (logged). """ cfg = _PLANET_CONFIGS.get(planet_num) if cfg is None: logger.warning('Unknown planet number %r for moon parsing', planet_num) return [] moon_ids = [moon.id for moon in cfg.moons if moon.id != cfg.planet_id] name_to_id = { moon.name.lower(): moon.id for moon in cfg.moons if moon.id != cfg.planet_id and moon.name.strip() } classical_map: dict[int, list[int]] = { 4: [401, 402], # Mars all 5: [501, 502, 503, 504], # Jupiter classical moons 6: [601, 602, 603, 604, 605, 606, 607, 608, 609], # Saturn S1-S9 7: [701, 702, 703, 704, 705], # Uranus U1-U5 8: [801, 802], # Neptune Triton + Nereid 9: [901], # Pluto Charon } cgi_group_map: dict[int, dict[int, list[int]]] = { # Viewer CGI group codes from the FORTRAN form. 4: {402: [401, 402]}, 5: { 504: [501, 502, 503, 504], 505: [501, 502, 503, 504, 505], 516: [501, 502, 503, 504, 505, 514, 515, 516], }, 6: { 609: [601, 602, 603, 604, 605, 606, 607, 608, 609], 618: [ 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, ], 653: [ 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 632, 633, 634, 635, 649, 653, ], }, 7: { 705: [701, 702, 703, 704, 705], 715: [701, 702, 703, 704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715], 727: [ 701, 702, 703, 704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 725, 726, 727, ], }, 8: {802: [801, 802], 814: [801, 802, 803, 804, 805, 806, 807, 808, 814]}, 9: {901: [901], 903: [901, 902, 903], 905: [901, 902, 903, 904, 905]}, } out: list[int] = [] seen: set[int] = set() def _append_unique(moon_id: int) -> None: if moon_id in seen: return seen.add(moon_id) out.append(moon_id) def _int_prefix(text: str) -> int | None: digits = '' for ch in text: if ch.isdigit(): digits += ch else: break if len(digits) == 0: return None return int(digits) for s in tokens: s = s.strip() if len(s) == 0: continue key = s.lower() if key == 'classical': for moon_id in classical_map.get(planet_num, moon_ids): _append_unique(moon_id) continue if key == 'all': for moon_id in moon_ids: _append_unique(moon_id) continue try: num = int(s) # FORTRAN viewer reads first 3 chars as group/max-ID; resolve group codes. if num in cgi_group_map.get(planet_num, {}): for moon_id in cgi_group_map[planet_num][num]: _append_unique(moon_id) continue if num >= 100: if num in moon_ids: _append_unique(num) else: logger.warning('Unknown NAIF moon ID %r for planet %s', num, planet_num) elif num >= 1: moon_id = 100 * planet_num + num if moon_id in moon_ids: _append_unique(moon_id) else: logger.warning('Unknown moon index %r for planet %s', num, planet_num) continue except ValueError: pref = _int_prefix(s) if pref is not None: num = pref # FORTRAN viewer reads string(1:3) and uses it as max moon ID or group # code; match that by resolving known group codes with or without text. if num in cgi_group_map.get(planet_num, {}): for moon_id in cgi_group_map[planet_num][num]: _append_unique(moon_id) continue if num >= 100: if num in moon_ids: _append_unique(num) else: logger.warning('Unknown NAIF moon ID %r for planet %s', num, planet_num) elif num >= 1: moon_id = 100 * planet_num + num if moon_id in moon_ids: _append_unique(moon_id) else: logger.warning('Unknown moon index %r for planet %s', num, planet_num) continue if key in name_to_id: _append_unique(name_to_id[key]) else: logger.warning('Unknown moon name %r for planet %s', s, planet_num) return out
_PLANET_LETTER: dict[int, str] = { 4: 'M', 5: 'J', 6: 'S', 7: 'U', 8: 'N', 9: 'P', }
[docs] def get_moon_display_name(planet_num: int, moon_id: int) -> str | None: """Return display name for moon (e.g. 'Ganymede (J3)'). Parameters: planet_num: Planet number (4-9). moon_id: NAIF moon body ID (e.g. 503 for Ganymede). Returns: Display string (name + letter index) or None if unknown. Raises: None. Invalid planet_num or moon_id yields None; no exceptions are raised. """ cfg = _PLANET_CONFIGS.get(planet_num) if cfg is None: return None moon = cfg.moon_by_id(moon_id) if moon is None: return None idx = moon_id % 100 if planet_num not in _PLANET_LETTER: logger.warning('Missing _PLANET_LETTER for planet_num=%s; moon=%s', planet_num, moon.name) return None letter = _PLANET_LETTER[planet_num] return f'{moon.name} ({letter}{idx})'
__all__ = [ 'JUPITER_CONFIG', 'MARS_CONFIG', 'NEPTUNE_CONFIG', 'PLUTO_CONFIG', 'SATURN_CONFIG', 'URANUS_CONFIG', 'ArcSpec', 'MoonSpec', 'PlanetConfig', 'RingSpec', 'get_moon_display_name', 'get_moon_name_to_index', 'parse_moon_spec', ]