Source code for ADCS.orbits.ephemeris

__all__ = ["Ephemeris"]

from pathlib import Path
from skyfield.api import load, Loader
from typing import Optional

[docs] class Ephemeris: r""" High-precision planetary ephemeris interface based on Skyfield. This class provides a unified interface for loading and accessing high-precision planetary ephemerides using the `Skyfield <https://rhodesmill.org/skyfield/>`_ library. It manages the retrieval, storage, and initialization of the JPL DE421 ephemeris file and exposes commonly used celestial bodies such as the Earth and the Sun. The ephemeris data are represented in the International Celestial Reference Frame (ICRF), enabling precise position and velocity computations required for orbital dynamics, attitude determination, and navigation applications. Mathematical Background ----------------------- A planetary ephemeris provides the barycentric position and velocity of solar system bodies as continuous functions of time: .. math:: \mathbf{r}_i(t), \; \mathbf{v}_i(t) \quad \forall i \in \{\text{Sun, Earth, Moon, planets}\} where :math:`\mathbf{r}_i` and :math:`\mathbf{v}_i` are expressed in the ICRF frame. These quantities are derived from numerical integration of the N-body equations of motion: .. math:: \ddot{\mathbf{r}}_i = \sum_{j \neq i} G m_j \frac{\mathbf{r}_j - \mathbf{r}_i} {\lVert \mathbf{r}_j - \mathbf{r}_i \rVert^3}, with relativistic and empirical corrections applied in the JPL models. The DE421 ephemeris is valid over the time span: .. math:: 1900 \le t \le 2050, and provides sufficient accuracy for most Earth-orbiting spacecraft analyses. :param filepath: Optional path to a pre-downloaded JPL DE421 ephemeris file. If ``None``, a default location under ``ADCS/environment/de421.bsp`` is used. :type filepath: pathlib.Path | None :raises RuntimeError: If the ephemeris file cannot be downloaded or loaded. .. note:: This class internally initializes a shared Skyfield :class:`~ADCS.etc.Ephemeris` timescale object, which should be reused across all time-dependent orbital computations to ensure consistency. """ def __init__(self, filepath: Optional[Path] = None) -> None: r""" Initialize the ephemeris loader and planetary body objects. This constructor loads the JPL DE421 ephemeris file either from a user-specified path or from the default project directory. If the file does not exist locally, it is automatically downloaded using Skyfield’s loader utilities. Upon successful loading, commonly used celestial bodies and a timescale object are extracted and stored as attributes. :param filepath: Optional filesystem path to a DE421 ephemeris file. If ``None``, a default project-relative path is used. :type filepath: pathlib.Path | None :return: ``None`` :rtype: None """ if filepath is not None: # User-provided file path self.planets = load(str(filepath)) else: # Default: search or download under project_root/ADCS/environment/de421.bsp default_path = self._get_default_ephemeris_path() if default_path.exists(): self.planets = load(str(default_path)) print(f"✅ Loaded local ephemeris from {default_path}") else: self.planets = self._download_ephemeris(default_path) # Extract common bodies self.sun = self.planets['sun'] self.earth = self.planets['earth'] # Create a timescale object (shared across all orbital computations) self.ts = load.timescale() # ---------------------------------------------------------------------- def _get_default_ephemeris_path(self) -> Path: r""" Determine the default local path for the DE421 ephemeris file. This method constructs the expected filesystem location for storing the ephemeris file relative to the project root. If the parent directory does not exist, it is created automatically. :return: Absolute path to ``ADCS/environment/de421.bsp``. :rtype: pathlib.Path """ project_root = Path(__file__).resolve().parents[2] # adjust if needed external_dir = project_root / "ADCS" / "environment" external_dir.mkdir(parents=True, exist_ok=True) return external_dir / "de421.bsp" # ---------------------------------------------------------------------- def _download_ephemeris(self, save_path: Path): r""" Download and load the JPL DE421 ephemeris file. This method uses Skyfield’s :class:`~ADCS.etc.Ephemeris` loader mechanism to retrieve the DE421 ephemeris from official JPL mirrors and store it at the specified location. If the file already exists, it is reused. :param save_path: Destination path where the ephemeris file will be stored. :type save_path: pathlib.Path :return: Loaded Skyfield planetary ephemeris kernel. :rtype: skyfield.jpllib.SpiceKernel :raises RuntimeError: If the ephemeris file cannot be downloaded or initialized. """ try: loader = Loader(str(save_path.parent)) planets = loader("de421.bsp") # downloads if missing print(f"✅ Ephemeris downloaded to: {planets.path}") return planets except Exception as e: raise RuntimeError(f"❌ Failed to download DE421 ephemeris: {e}") from e