special_observatories

Resolve space-based (“special case”) MPC observatory codes via JPL Horizons.

A handful of MPC observatory codes denote spacecraft rather than fixed ground stations (issue #55). They have no parallax constants, so layup cannot compute their position geometrically, and – unlike a roving ground observer – the user usually does not carry an explicit per-observation position for them. For these codes we query the JPL Horizons vector API for the spacecraft’s geocentric state at each observation epoch and feed it into the same moving-observer path that an ADES-supplied position uses (issue #147).

The obscode -> NAIF-id map is ported from the Rust spacerocks project (~/Dropbox/roman-footprint/spacerocks). Spacecraft carry negative NAIF ids by JPL convention.

This is intentionally a live HTTP lookup (the case is rare); a user-supplied ADES position always takes priority, so fits never depend on Horizons being reachable when a position is provided.

Attributes

logger

HORIZONS_API

SPACE_OBSERVATORIES

J2000_JD

SECONDS_PER_DAY

_TLIST_CHUNK

Functions

et_to_jd_tdb(et)

SPICE ephemeris time (seconds past J2000 TDB) -> Julian Date (TDB).

is_space_observatory(obscode)

Whether obscode is a spacecraft we resolve via JPL Horizons.

query_horizons_geocentric(naif_id, jd_tdb_list[, ...])

Geocentric ICRF state of a spacecraft at the given epochs, via JPL Horizons.

_query_horizons_uncached(naif_id, jd_list, timeout)

The raw chunked Horizons query, with no disk cache (one network round-trip

_horizons_cache_root(cache_dir)

Resolve the persistent-cache root directory, or None to disable caching.

_cache_file(cdir, jd)

_cache_load(cdir, jd)

_cache_store(cdir, jd, state)

Best-effort atomic write of one (pos, vel) state. Distinct epochs are

_query_chunk(naif_id, jd_chunk, timeout)

_parse_vectors(text, jd_chunk, naif_id)

Parse the CSV vector table between the $$SOE/$$EOE markers.

Module Contents

logger[source]
HORIZONS_API = 'https://ssd.jpl.nasa.gov/api/horizons.api'[source]
SPACE_OBSERVATORIES[source]
J2000_JD = 2451545.0[source]
SECONDS_PER_DAY = 86400.0[source]
_TLIST_CHUNK = 40[source]
et_to_jd_tdb(et)[source]

SPICE ephemeris time (seconds past J2000 TDB) -> Julian Date (TDB).

is_space_observatory(obscode)[source]

Whether obscode is a spacecraft we resolve via JPL Horizons.

query_horizons_geocentric(naif_id, jd_tdb_list, timeout=30, cache_dir=None)[source]

Geocentric ICRF state of a spacecraft at the given epochs, via JPL Horizons.

Parameters:
  • naif_id (str) – JPL/NAIF id of the spacecraft (e.g. "-48" for Hubble).

  • jd_tdb_list (sequence of float) – Observation epochs as Julian Date (TDB).

  • timeout (float, optional) – Per-request HTTP timeout in seconds.

  • cache_dir (str or path or None or False, optional) – Location of the persistent (naif_id, jd) -> state disk cache. The states are geometric (VEC_CORR='NONE') and therefore deterministic and safe to cache across objects, processes, and runs – one Horizons call per (spacecraft, epoch), ever, instead of once per object per process (which rate-limits to HTTP 503 at catalog scale). Precedence: an explicit cache_dir (uses a horizons/ subdir under it) > the LAYUP_HORIZONS_CACHE env var > the default layup pooch cache. Pass cache_dir=False (or LAYUP_HORIZONS_CACHE=0) to disable.

Returns:

{jd_tdb: (pos_km, vel_km_s)}; each value is a length-3 numpy array. Positions are geocentric (center = Earth body center), ICRF / equatorial-J2000, in km, and velocities in km/s. No light-time or aberration correction is applied (VEC_CORR='NONE'): the geometric state at the requested TDB epoch is returned, matching how layup supplies every other observer position (light time is handled later, in the integrator).

Return type:

dict

Notes

We deliberately query the geocentric spacecraft state (center = Earth’s body center) rather than the barycentric one, so this plugs into the same “geocentric observer offset + Earth’s barycentric state” path the ground stations use: _barycentric_moving_observatory adds Earth’s barycentric state (from layup’s own SPICE kernel) to this offset. Referencing the offset against a physical body (Earth) is more reproducible than referencing against the solar-system barycenter, whose definition shifts between ephemeris versions.

Caveat for high-precision work: Earth’s geocenter as realized by layup’s SPICE kernel may differ slightly from the geocenter implied by the spacecraft’s Horizons reference ephemeris. The geocentric offset cancels Earth’s own position to first order, but a small residual inconsistency (~meters) can remain. That is negligible for normal astrometry, but could matter for very high precision applications such as space-based stellar occultations – flagged here as a known limitation (see PR #377 discussion).

_query_horizons_uncached(naif_id, jd_list, timeout)[source]

The raw chunked Horizons query, with no disk cache (one network round-trip per _TLIST_CHUNK epochs).

_horizons_cache_root(cache_dir)[source]

Resolve the persistent-cache root directory, or None to disable caching.

See query_horizons_geocentric for the precedence. Returns a Path whose per-spacecraft subdirectories hold one <jd>.npz per cached epoch.

_cache_file(cdir, jd)[source]
_cache_load(cdir, jd)[source]
_cache_store(cdir, jd, state)[source]

Best-effort atomic write of one (pos, vel) state. Distinct epochs are distinct files, so this is lock-free and safe on a shared/parallel filesystem; a temp-file-plus-rename keeps a concurrent reader from seeing a partial file. A cache write must never break a fit, so failures are swallowed.

_query_chunk(naif_id, jd_chunk, timeout)[source]
_parse_vectors(text, jd_chunk, naif_id)[source]

Parse the CSV vector table between the $$SOE/$$EOE markers.

With CSV_FORMAT='YES', VEC_TABLE='2' and VEC_LABELS='NO' each data row is JDTDB, CalendarDate, X, Y, Z, VX, VY, VZ (trailing comma).