Source code for empyrean.coordinates.enums
"""Reference frame and origin types."""
import enum
from dataclasses import dataclass
from typing import ClassVar
[docs]
class Frame(str, enum.Enum):
"""Reference frame for coordinate states.
Subclasses ``str`` so values serialize directly into Arrow / quivr
string columns.
"""
ICRF = "icrf"
"""International Celestial Reference Frame (inertial, equatorial)."""
ECLIPTICJ2000 = "eclipticj2000"
"""J2000 ecliptic frame (inertial, ecliptic plane)."""
ITRF93 = "itrf93"
"""Earth body-fixed rotating frame (ITRF93).
Requires a high-precision Earth-orientation BPC kernel
(``earth_latest_high_prec.bpc`` or equivalent) to be loaded.
:func:`empyrean.initialize` stages this kernel automatically when
the ``naif-eop-high-prec`` PyPI package is installed; lazy fetch
otherwise.
"""
[docs]
@dataclass(frozen=True, eq=True)
class Origin:
"""Origin (center body) for a coordinate state.
Use the class-level constants for named bodies::
Origin.EARTH
Origin.SUN
Origin.JUPITER_BARYCENTER
For numbered asteroids, use the factory::
Origin.asteroid(1) # Ceres
Origin.asteroid(99942) # Apophis
Origin instances are immutable, hashable, and serialize to a stable
canonical string when stored in quivr columns (``"Earth"``,
``"asteroid_1"``). Compare with ``==``.
Mercury, Venus, Earth, and the Moon resolve to body centres.
Mars through Pluto are exposed as ``*_BARYCENTER`` aliases.
Origins are accepted in **all** API arguments that take a body
reference (``body_filter``, ``excluded_perturbers``, the
``origin`` field on coordinate types, etc.). String forms
(``"Earth"``, ``"asteroid_1"``) are also accepted via
:meth:`Origin.from_string`.
"""
name: str
"""Canonical body name. ``"Earth"`` / ``"Sun"`` / ``"asteroid_1"``."""
# Class-level constants for the named bodies. Declared here so the
# type checker knows they exist; the instances are assigned below the
# class body (see end of file) once the dataclass machinery is wired.
SSB: ClassVar["Origin"]
SUN: ClassVar["Origin"]
MERCURY: ClassVar["Origin"]
VENUS: ClassVar["Origin"]
EARTH: ClassVar["Origin"]
MOON: ClassVar["Origin"]
MARS_BARYCENTER: ClassVar["Origin"]
JUPITER_BARYCENTER: ClassVar["Origin"]
SATURN_BARYCENTER: ClassVar["Origin"]
URANUS_BARYCENTER: ClassVar["Origin"]
NEPTUNE_BARYCENTER: ClassVar["Origin"]
PLUTO_BARYCENTER: ClassVar["Origin"]
[docs]
@classmethod
def asteroid(cls, number: int) -> "Origin":
"""Construct an origin for a numbered asteroid (IAU number).
Examples::
Origin.asteroid(1) # Ceres
Origin.asteroid(4) # Vesta
Origin.asteroid(99942) # Apophis
"""
if not isinstance(number, int) or number <= 0:
raise ValueError(f"asteroid number must be a positive integer, got {number!r}")
return cls(f"asteroid_{number}")
[docs]
@classmethod
def from_string(cls, s: str) -> "Origin":
"""Parse a canonical name (case-sensitive class attributes,
case-insensitive otherwise).
Inverse of ``str(origin)``. Useful for re-hydrating origins
from text columns or human-typed config.
"""
if not isinstance(s, str):
raise TypeError(f"Origin.from_string expects str, got {type(s).__name__}")
# Asteroid encoding round-trips via the factory.
if s.startswith("asteroid_"):
try:
n = int(s[9:])
except ValueError as e:
raise ValueError(f"unparseable asteroid name: {s!r}") from e
return cls.asteroid(n)
# Direct match on the class attribute names (canonical strings).
for known in _NAMED_ORIGINS:
if s == known.name:
return known
# Lower-case fallback so things like "earth" / "ssb" / "jupiter_barycenter"
# still resolve. Stay strict on whitespace; only accept exactly the
# known canonical or canonical-with-underscores form.
lower = s.strip().lower().replace(" ", "_")
for known in _NAMED_ORIGINS:
if lower == known.name.lower().replace(" ", "_"):
return known
# A few common short aliases.
aliases = {
"ssb": cls("SSB"),
"mars": cls("Mars Barycenter"),
"jupiter": cls("Jupiter Barycenter"),
"saturn": cls("Saturn Barycenter"),
"uranus": cls("Uranus Barycenter"),
"neptune": cls("Neptune Barycenter"),
"pluto": cls("Pluto Barycenter"),
}
if lower in aliases:
return aliases[lower]
raise ValueError(f"unknown origin: {s!r}")
def __str__(self) -> str:
return self.name
# Class-level constants for the named bodies. Populated below the class
# body so the dataclass machinery is fully wired before we try to
# construct instances.
Origin.SSB = Origin("SSB")
"""Solar System Barycenter."""
Origin.SUN = Origin("Sun")
"""Sun center."""
Origin.MERCURY = Origin("Mercury")
"""Mercury center."""
Origin.VENUS = Origin("Venus")
"""Venus center."""
Origin.EARTH = Origin("Earth")
"""Earth center."""
Origin.MOON = Origin("Moon")
"""Moon center."""
Origin.MARS_BARYCENTER = Origin("Mars Barycenter")
"""Mars system barycenter."""
Origin.JUPITER_BARYCENTER = Origin("Jupiter Barycenter")
"""Jupiter system barycenter."""
Origin.SATURN_BARYCENTER = Origin("Saturn Barycenter")
"""Saturn system barycenter."""
Origin.URANUS_BARYCENTER = Origin("Uranus Barycenter")
"""Uranus system barycenter."""
Origin.NEPTUNE_BARYCENTER = Origin("Neptune Barycenter")
"""Neptune system barycenter."""
Origin.PLUTO_BARYCENTER = Origin("Pluto Barycenter")
"""Pluto system barycenter."""
# Internal — used by `Origin.from_string` to enumerate the named
# bodies. Tests can rely on this list being complete.
_NAMED_ORIGINS = (
Origin.SSB,
Origin.SUN,
Origin.MERCURY,
Origin.VENUS,
Origin.EARTH,
Origin.MOON,
Origin.MARS_BARYCENTER,
Origin.JUPITER_BARYCENTER,
Origin.SATURN_BARYCENTER,
Origin.URANUS_BARYCENTER,
Origin.NEPTUNE_BARYCENTER,
Origin.PLUTO_BARYCENTER,
)