Source code for empyrean.observers.observers
"""Observer state type."""
from collections.abc import Sequence
import numpy as np
import quivr as qv
from empyrean.coordinates.coordinates import CartesianCoordinates
from empyrean.coordinates.enums import Frame, Origin
from empyrean.coordinates.epoch import Epochs
[docs]
class Observers(qv.Table):
"""Precomputed observer states as Cartesian coordinates.
Each row is one MPC observatory at one epoch. The kinematic state
lives inside a nested :class:`CartesianCoordinates` table — same
schema as orbit positions — so frame and origin are explicit and
consistent with the rest of the API. By default that's
:attr:`Frame.ICRF` and :attr:`Origin.SSB`.
Construct via :meth:`from_code` (single observatory, ``N`` epochs)
or :meth:`from_codes` (cartesian product of ``N`` observatories ×
``M`` epochs). The top-level shortcut
:func:`empyrean.get_observer_states` is the same as
:meth:`from_codes`.
"""
obs_code = qv.LargeStringColumn()
coordinates = CartesianCoordinates.as_column()
observing_night = qv.Int32Column(nullable=True) # YYYYMMDD or null
[docs]
@classmethod
def from_code(
cls,
obs_code: str,
epochs: Epochs | np.ndarray | Sequence[float],
) -> "Observers":
"""Observer states for a **single** observatory at ``N`` epochs.
Returns an ``N``-row table, one row per epoch.
Parameters
----------
obs_code : str
MPC observatory code (e.g. ``"W84"``, ``"F51"``, ``"274"``).
epochs : Epochs | array-like
``N`` observation epochs. An :class:`Epochs` table is
converted to TDB internally; an array is treated as MJD TDB.
Returns
-------
Observers
Length-``N`` table — row ``i`` is the observer at ``epochs[i]``.
Examples
--------
>>> times = Epochs.from_mjd([60500.0, 60501.0])
>>> obs = Observers.from_code("W84", times)
>>> len(obs)
2
"""
return cls.from_codes([obs_code], epochs)
[docs]
@classmethod
def from_codes(
cls,
obs_codes: Sequence[str],
epochs: Epochs | np.ndarray | Sequence[float],
) -> "Observers":
"""Observer states for the **cartesian product** of ``N``
observatory codes × ``M`` epochs.
Returns an ``N * M``-row table in **code-major** order: all
``M`` epochs for ``obs_codes[0]``, then all ``M`` epochs for
``obs_codes[1]``, and so on. Indexing into the result is
therefore ``i * M + j`` for the (code ``i``, epoch ``j``) row.
Parameters
----------
obs_codes : list[str]
``N`` MPC observatory codes.
epochs : Epochs | array-like
``M`` observation epochs. An :class:`Epochs` table is
converted to TDB internally; an array is treated as MJD TDB.
Returns
-------
Observers
``N * M``-row table, code-major.
Examples
--------
>>> times = Epochs.from_mjd([60000.0, 60001.0])
>>> obs = Observers.from_codes(["W84", "F51", "274"], times)
>>> len(obs)
6
>>> obs.obs_code.to_pylist()
['W84', 'W84', 'F51', 'F51', '274', '274']
"""
from empyrean._empyrean_rs import _get_observers
if isinstance(epochs, Epochs):
tdb = epochs.to_tdb()
epochs_mjd = np.asarray(tdb.mjd.to_numpy(zero_copy_only=False), dtype=np.float64)
else:
epochs_mjd = np.asarray(epochs, dtype=np.float64)
result = _get_observers(list(obs_codes), epochs_mjd)
nights = result["observing_night"]
night_list = [int(n) if n >= 0 else None for n in nights]
n_rows = len(result["epoch"])
coordinates = CartesianCoordinates.from_kwargs(
epoch=np.asarray(result["epoch"]),
x=np.asarray(result["x"]),
y=np.asarray(result["y"]),
z=np.asarray(result["z"]),
vx=np.asarray(result["vx"]),
vy=np.asarray(result["vy"]),
vz=np.asarray(result["vz"]),
frame=Frame.ICRF.value,
origin=[str(Origin.SSB)] * n_rows,
)
return cls.from_kwargs(
obs_code=result["obs_code"],
coordinates=coordinates,
observing_night=night_list,
)