curryer.compute.geometry¶
Selective geometric data-field computation.
This module computes the geolocation/geometry ancillary data fields that geolocated products commonly need. It is organized in two layers, both mission-generic:
Math-only leaf functions (
sc_radius,colatitude,subobserver_point,earth_sun_distance,satellite_altitude) – pure, vectorized, SPICE-free. They take high-level inputs (positions) as arguments and can be called directly to compose custom fields; the coordinate/frame primitives they build on (e.g.ecef_to_geodetic) stay incurryer.compute.spatial.A selective-compute registry – a declarative map from each output field to the SPICE-derived inputs (“providers”) it needs. A caller requests an arbitrary subset of fields and only the inputs that subset needs are queried, each provider exactly once, never per-field. This is the pre-built convenience over the leaf tools.
Why a registry rather than one method per field: the field-to-input mapping is many-to-many and growing. The registry computes the minimal provider set for any requested subset automatically; the naive alternative (each field querying its own inputs) re-queries SPICE redundantly.
Two public accessors are exposed on GeometryData:
get_geometry(ugps_times, fields)->pandas.DataFrame– the primary, tabular API. Vector fields expand to per-field-prefixed columns.get_vectors(ugps_times, fields)->{field: (N, k) ndarray}– the typed sibling, addressed by field name rather than by string-built column prefixes.
Mission genericity: the observing body is the only mission input (a constructor
argument). Only universal identifiers (EARTH, SUN, ITRF93) appear in
code – no spacecraft/instrument names are hardcoded.
Frame contract: the Earth-fixed (ECEF) fields all share a single reference frame
– the earth_frame configured on GeometryData (ITRF93 by
default) – so fields are never combined in mismatched reference frames. The
*_inertial fields likewise share inertial_frame (J2000 by default),
and satellite_attitude targets attitude_frame (defaulting to
inertial_frame). A field’s frame is named in its description; the Earth-fixed
and inertial state vectors are distinct fields, never the same field re-framed.
Fill contract: SPICE coverage gaps (and off-Earth samples) surface as NaN. The
providers query with allow_nans (True by default), so an uncovered time
yields a NaN provider row; the math-only leaves are pure and propagate NaN
elementwise, so every field is NaN on exactly the rows its inputs are missing and
finite elsewhere – per time, per field. Downstream maps those NaNs onto the
product _FillValue (e.g. -999); the rows are never dropped or back-filled.
Available fields: request any by name (GeometryData.available_fields() lists
them). Each field expands to the columns below.
sc_radius->spacecraft_radius– observer distance from Earth center.subsatellite->subsatellite_latitude,subsatellite_longitude,subsatellite_colatitude– ground point beneath the spacecraft.subsolar->subsolar_latitude,subsolar_longitude,subsolar_colatitude– ground point beneath the Sun.earth_sun_distance->earth_sun_distance– Earth-Sun distance (AU).sc_position->spacecraft_position_x,spacecraft_position_y,spacecraft_position_z– spacecraft position (ECEF).sc_altitude->spacecraft_altitude– observer geodetic height above the ellipsoid (km).boresight->boresight_x,boresight_y,boresight_z– instrument boresight unit vector (ECEF).surface_colatitude->surface_colatitude– colatitude of the boresight ellipsoid intersection.viewing_zenith/solar_zenith-> same-named columns – geodetic zenith of the satellite / Sun at the boresight ellipsoid intersection.viewing_azimuth/solar_azimuth-> same-named columns – satellite / Sun azimuth at that intersection.relative_azimuth->relative_azimuth– viewing relative to solar azimuth.cone_angle->cone_angle– boresight angle off the satellite-to-geocenter vector.cone_angle_rate->cone_angle_rate– finite-difference time derivative ofcone_angle(deg/s) over the requested times.sc_velocity->spacecraft_velocity_x/y/z– observer velocity (ECEF, km/s).satellite_attitude->attitude_q0..q3– observer body attitude quaternion (body ->attitude_frame, scalar-first).clock_angle/clock_angle_rate-> same-named columns – boresight azimuth in the inertial-velocity orbital frame (CERES SCI-12) and its unwrapped rate (deg/s).along_track_angle/cross_track_angle-> same-named columns – boresight look angles from nadir in the velocity-nadir and cross-track-nadir planes.sc_position_inertial/sc_velocity_inertial->spacecraft_position_inertial_x/y/z/spacecraft_velocity_inertial_x/y/z– observer state ininertial_frame(km, km/s).boresight_inertial->boresight_inertial_x/y/z– instrument boresight unit vector ininertial_frame.moon_direction->moon_direction_x/y/z– unit vector toward the Moon, in the instrument’s FOV frame.moon_angular_radius->moon_angular_radius– apparent angular radius of the lunar disk.moon_distance->moon_distance– apparent observer-Moon distance (km).
Angle convention: the surface angles are in degrees, over the boresight ellipsoid
intersection. Azimuths (viewing_azimuth, solar_azimuth) are clockwise from
geodetic North in [0, 360); zeniths (viewing_zenith, solar_zenith) are
geodetic, from the local surface normal, in [0, 180]. relative_azimuth uses the
CERES BDS R3V4 origin – mod(viewing_azimuth - solar_azimuth + 180, 360), so the
Sun sits at 180 – and is the lossless unfolded value; the CERES [0, 180] fold
(min(raa, 360 - raa)) is a separate, lossy, downstream step. cone_angle is
in [0, 90] for Earth-disk views.
The lunar fields are a direction rather than surface angles. moon_direction_x/y/z is
the unit vector from the observer to the Moon in the frame the instrument kernel declares
its FOV in (INS<id>_FOV_FRAME), which is the frame the boresight and FOV boundary
vectors already live in – so the boresight separation is a dot product against the
boresight from curryer.spicierpy.ext.instrument_fov, with no rotation in between.
A caller wanting a boresight-relative azimuth/elevation pair passes those same two
vectors to curryer.compute.spatial.boresight_offset_angles, which takes the azimuth
origin from the IK’s own FOV_REF_VECTOR.
The direction is to the center of the Moon, not its limb: the nearest edge of the disk
sits moon_angular_radius closer than the center direction implies. The target is the
Moon body center (NAIF 301), not the Earth-Moon barycenter, and the angular radius uses
the largest of the body’s three radii, so the disk circumscribes rather than understates.
The Moon is queried as an apparent (LT+S) position, so moon_distance is the
light-time corrected range.
Attributes¶
Classes¶
Selective geometric data-field server. |
Functions¶
|
Distance of the observer from the body center (vectorized). |
|
Convert geodetic latitude to colatitude (vectorized). |
|
Sub-observer geodetic latitude, longitude, and colatitude (vectorized). |
|
Distance between Earth and Sun (vectorized). |
|
Geodetic altitude of the observer above the ellipsoid (vectorized). |
Module Contents¶
- curryer.compute.geometry.logger¶
- curryer.compute.geometry.sc_radius(observer_position: numpy.ndarray) numpy.ndarray¶
Distance of the observer from the body center (vectorized).
Implements the “radius of satellite from center of Earth” field.
- Parameters:
observer_position (np.ndarray) – Observer (e.g., spacecraft) positions in rectangular coordinates, shape (…, 3). Units are arbitrary; the result is in the input’s units.
- Returns:
Euclidean distance
||position||per point, shape (…,).- Return type:
np.ndarray
- curryer.compute.geometry.colatitude(latitude: numpy.ndarray, degrees: bool = True) numpy.ndarray¶
Convert geodetic latitude to colatitude (vectorized).
Colatitude is the complement of latitude (
90 - lat), ranging from 0 at the north pole to 180 at the south pole. Implements the surface-point colatitude field; also reused by the sub-point fields.- Parameters:
latitude (np.ndarray) – Geodetic latitude.
degrees (bool, optional) – If True (default), inputs and outputs are in degrees, otherwise radians.
- Returns:
Colatitude in the same units as the input.
- Return type:
np.ndarray
- curryer.compute.geometry.subobserver_point(observer_position: numpy.ndarray, degrees: bool = True) numpy.ndarray¶
Sub-observer geodetic latitude, longitude, and colatitude (vectorized).
The sub-observer point is the geodetic ground point directly beneath the observer; it shares the observer’s geodetic latitude and longitude. Generic over the observing body: pass the spacecraft position for the sub-satellite point or the Sun position for the sub-solar point.
- Parameters:
observer_position (np.ndarray) – Observer positions in ECEF rectangular coordinates (km), shape (…, 3).
degrees (bool, optional) – If True (default), latitude/longitude/colatitude are in degrees, otherwise radians. Longitude follows
ecef_to_geodetic(-180 to 180).
- Returns:
Stacked
[latitude, longitude, colatitude], shape (…, 3).- Return type:
np.ndarray
- curryer.compute.geometry.earth_sun_distance(earth_sun_position: numpy.ndarray, au: bool = True) numpy.ndarray¶
Distance between Earth and Sun (vectorized).
Implements the Earth-Sun distance field. The input is the Earth-to-Sun position vector (any frame, since only the magnitude is used).
- Parameters:
earth_sun_position (np.ndarray) – Earth-to-Sun position in rectangular coordinates (km), shape (…, 3).
au (bool, optional) – If True (default), return astronomical units, otherwise kilometers.
- Returns:
Earth-Sun distance per point, shape (…,).
- Return type:
np.ndarray
- curryer.compute.geometry.satellite_altitude(observer_position: numpy.ndarray) numpy.ndarray¶
Geodetic altitude of the observer above the ellipsoid (vectorized).
The height-above-ellipsoid component of the observer’s geodetic position – the piece
subobserver_pointdrops when it returns latitude/longitude/colatitude. Complementssc_radius(the geocentric distance from the body center).- Parameters:
observer_position (np.ndarray) – Observer (e.g., spacecraft) positions in ECEF rectangular coordinates (km), shape (…, 3).
- Returns:
Geodetic altitude in km, shape (…,).
- Return type:
np.ndarray
- class curryer.compute.geometry.GeometryData(observer, microsecond_cadence=None, earth='EARTH', sun='SUN', moon='MOON', earth_frame=None, inertial_frame='J2000', attitude_frame=None)¶
Bases:
curryer.compute.abstract.AbstractMissionDataSelective geometric data-field server.
Construct with the observing body, then request any subset of registered fields via
get_geometry()(DataFrame) orget_vectors()(typed{field: ndarray}). Only the SPICE inputs the requested subset needs are queried, each exactly once. Relevant kernels must already be loaded for the requested times, mirroring the othercomputeservers.The requested times are the independent variable:
get_geometry()andget_vectors()evaluate every field at exactly theugps_timespassed – an arbitrary, possibly non-uniform array – with no resampling or interpolation.microsecond_cadence/get_times()only offers an optional uniform grid for callers that want one; it never constrains which times are queried.Scope – this server produces per-observation ancillary geometry: one row per requested time. Its fields are of two kinds: spacecraft-level quantities that are independent of pointing (subsatellite/subsolar points, radius, altitude, Earth-Sun distance) and angles referenced to the instrument’s single boresight – the IK boresight (viewing/solar zenith and azimuth, relative azimuth, cone angle, surface colatitude). It deliberately does not compute per-pixel geometry: an instrument with many pointing vectors (a camera focal plane, an offset pixel) is a
(time, pixel)problem served bycompute_ellipsoid_intersection()(custom_pointing_vectors=), whose vectorized ray-cast and per-pixel quality flags belong with the geolocated product rather than this ancillary table. A future mission needing a single non-boresight reference vector here (still one vector, not the array) is a natural additive extension, not a change to this contract.- Parameters:
observer (str or int or spicierpy.obj.Body) – Observing body whose position is queried – typically the instrument or spacecraft.
microsecond_cadence (int, optional) – Default cadence for
get_times(), in microseconds.earth (str or int or spicierpy.obj.Body, optional) – Central body, solar body and lunar body for the ephemeris queries. Default
"EARTH"/"SUN"/"MOON"; overridable so no body name is fixed in code.sun (str or int or spicierpy.obj.Body, optional) – Central body, solar body and lunar body for the ephemeris queries. Default
"EARTH"/"SUN"/"MOON"; overridable so no body name is fixed in code.moon (str or int or spicierpy.obj.Body, optional) – Central body, solar body and lunar body for the ephemeris queries. Default
"EARTH"/"SUN"/"MOON"; overridable so no body name is fixed in code.earth_frame (str or spicierpy.obj.Frame, optional) – Earth-fixed (ECEF) reference frame. Default
spatial.EARTH_FRAME(ITRF93).inertial_frame (str or spicierpy.obj.Frame, optional) – Inertial reference frame for the inertial state and the orbital-frame fields. Default
"J2000"; overridable so no frame is fixed in code.attitude_frame (str or spicierpy.obj.Frame, optional) – Target frame of the
satellite_attitudequaternion. Defaults toinertial_frame. It is a separate knob because a product may reference its attitude to a frame other than the one its state vectors use – e.g. an Earth-fixed attitude quaternion alongside inertial position/velocity.
- DEFAULT_CADENCE = 60000000¶
- observer¶
- earth = 'EARTH'¶
- sun = 'SUN'¶
- moon = 'MOON'¶
- earth_frame = 'ITRF93'¶
- inertial_frame = 'J2000'¶
- attitude_frame = 'J2000'¶
- classmethod available_fields()¶
Registered fields as
GeometryFieldmembers.Members are plain strings, so the result is usable directly as
fields=selectors and each member carries itscolumnsanddescription.
- get_geometry(ugps_times: numpy.ndarray, fields: list[str] | None = None) pandas.DataFrame¶
Compute the requested fields as a table.
- Parameters:
ugps_times (array_like of int) – One or more times in GPS microseconds. Arbitrary and need not be uniformly spaced; each time is evaluated exactly (no interpolation).
fields (list of str, optional) – Field names to compute. Default is the ephemeris-only set (valid for any observer); attitude/instrument fields (e.g.
boresight) must be requested explicitly. Seeavailable_fields()for the full list.
- Returns:
One row per time (index
ugps); vector fields expand to per-field-prefixed columns (e.g.spacecraft_position_x, spacecraft_position_y, spacecraft_position_z). Times outside SPICE coverage are NaN across that field’s columns (see the module Fill contract).- Return type:
pandas.DataFrame
- get_vectors(ugps_times: numpy.ndarray, fields: list[str]) dict[str, numpy.ndarray]¶
Compute the requested fields as typed arrays.
The typed sibling of
get_geometry(), addressed by field name rather than by string-built column prefixes.- Parameters:
ugps_times (array_like of int) – One or more times in GPS microseconds. Arbitrary and need not be uniformly spaced; each time is evaluated exactly (no interpolation).
fields (list of str) – Field names to compute.
- Returns:
dict of {str – Maps each field name to its
(N, k)array. Vector fields (e.g.sc_position) are(N, 3)in the configured Earth-fixed frame (ITRF93by default). Rows outside SPICE coverage are NaN (see the module Fill contract).- Return type:
numpy.ndarray}