Orbit Ingest
The ingest stage converts raw satellite ephemerides into a uniformly sampled, body-fixed orbit
catalogue that downstream simulation and estimation stages consume. The implementation lives in
src/orbit_processing.py and relies on SPICE frame transformations exposed through spiceypy,
furnished by the kernels referenced in the scenario file.
Processing flow
Running poetry run acons ingest … executes the following steps for every satellite entry listed
under constellation:
- Furnish SPICE kernels –
src/spice_support.py:furnish_kernelsloads the kernel list fromspice.kernels. A leapseconds kernel (LSK), a frame kernel (FK) for the inertial frame, and a binary/polynomial frame kernel (PCK/CK/BC) for the body-fixed frame are the minimum set. - Resolve the ingest timeline –
get_time_configaccepts absolute UTC/JD values or offsets in seconds from the first sample in the ephemerides (viatime.start_seconds/time.end_seconds). The helperbuild_time_gridcreates an inclusive Julian Date grid with the configured cadence. - Load and normalise ephemerides –
load_orbit_csvexpects comma-separated files with at leastjd,x_m,y_m,z_m,vx_mps,vy_mps,vz_mps. Columns are interpreted in the frame named byframes.input(typicallyJ2000). An optionalepoch_scolumn is recomputed if missing. - Interpolation onto the grid –
resample_states_leapfrogapplies a constant-acceleration leapfrog interpolator so every satellite has state vectors aligned to the ingest grid. - Frame conversion –
transform_statescallsspice.sxformviaspiceypyto obtain the 6×6 state transformation fromframes.inputtoframes.body_fixedat each epoch. Julian Dates are first converted to ephemeris time usingspice.unitim. - Export artefacts – a parquet file per satellite (
<sat_id>_orbit.parquet) plus an aggregateorbits_manifest.parquetare written to the ingest run directory. Each record contains epoch-relative seconds, Julian Date, ephemeris time, and body-fixed position/velocity. Quick-look plots are produced from both the input-frame states (plots/input_frame/) and the converted body-fixed orbits (plots/output_frame/) unless you run the CLI with--disable-plots, so you can inspect the transformation results; the 3D view in each frame now overlays a translucent central-body sphere and the receiver location for easy visual context. The ingest stage also emitsingest_ground_tracks.png, drawn against the Clementinedata/mosaic/PIA02992.tifmosaic, to show how each satellite sweeps the surface. Each directory also includesingest_altitude_velocity.png, plotting altitude (left axis) and velocity magnitude (right axis) for every satellite across the ingest window, plusingest/altitude_velocity_stats.csvcapturing mean, standard deviation, and extrema (min/max) of both metrics per satellite.
If the ingest window extends outside the supplied ephemeris coverage, the interpolator raises an error with the offending timestamps, prompting you to widen the source data or tighten the ingest interval.
Trace logging
Run poetry run acons ingest --log-level TRACE … to capture high-detail JSON trace entries in
<run-dir>/ingest/ingest.log. The ingest stage now records the resolved receiver longitude,
latitude, height, and height-source along with the exact scenario time window (start, end, cadence)
and every frame conversion pair (input frame, output frame, satellite count). TRACE also names the
satellite for every “Processed orbit” entry, including the frame conversion and sample count so you
can confirm each ephemeris made it through the ingest window, and enumerates the individual ingest
visualisations (input-frame plots, body-fixed plots, ground track, and stats) emitted for the run.
Enabling TRACE makes it easy to confirm that configuration inputs line up with the SPICE assets before
downstream stages consume the processed orbits.
Required inputs
- SPICE kernels: At minimum furnish
- an LSK (e.g.
naif0012.tls) for TDB↔UTC conversions, - an FK describing the inertial frame used by the ephemerides (e.g.
moon_080317.tffor the Moon PA frame definitions), - a body orientation kernel (binary PCK or CK). For Moon and Mars these are typically binary
PCKs (
*.bpc), whereas Earth commonly uses high-accuracy CK/ITRF solutions. The exact mix depends on the frames you transform between; consult NAIF documentation if you need high-fidelity orientation or tidal models. - Ephemeris files: One CSV per satellite holding inertial states. The filename stem becomes the
satellite identifier unless overridden by
constellation[].id. Keep epochs monotonically increasing; gaps larger than the ingest cadence lead to constant-acceleration interpolation across the gap. - Scenario file: Defines
frames.input,frames.body_fixed, ingest cadence (time.step_seconds), and optional relative start/end offsets. The user block provides body radius for plotting but does not influence the state conversion. For the receiver height you can (a) provideuser.location.height_mexplicitly, (b) setheight_m: demto automatically sampledata/mars_dem/global/Mars_HRSC_MOLA_BlendDEM_Global_200mp_v2.tif(Mars) ordata/moon_dem/global/Lunar_LRO_LOLA_Global_LDEM_118m_Mar2014.tif(Moon) with bilinear interpolation, or (c) add ademblock (ordem_path/dem_methodpair) either underuseroruser.locationto point at a different raster/method. All paths are resolved relative to the repo root.
Adapting to other bodies
Only the scenario configuration and SPICE kernel list need to change to ingest orbits for a different central body.
- Mars: Set
frames.body_fixed: "IAU_MARS"(or the appropriate Mars-fixed frame) and furnish:naif0012.tls, the NAIF Mars frame kernelpck00011.tpc(used by ACONS for the Mars IAU transformations), and a binary orientation kernel such asmar097.bsp/mar097.bpcthat carries the body-fixed rotation. Provide Mars-centric inertial ephemerides (e.g. inJ2000). Update user geometry with Mars’ mean radius if you are plotting surface references. - Earth: Choose a body-fixed frame compatible with your SPICE set—
ITRF93,IAU_EARTH, orEARTH_FIXED. Furnish an Earth FK (e.g.earth_latest_high_prec.tf) plus the appropriate high-precision Earth orientation kernels (earth_latest_high_prec.bpcor EOP CKs). Ensure the inertial ephemerides align with the same inertial frame (usuallyJ2000). Because Earth orientation files can be time-limited, verify the ingest timeline remains within the kernel span.
For any new body the workflow is:
frames:
input: "J2000"
body_fixed: "IAU_MARS" # or the target body's fixed frame
spice:
kernels:
- data/SPICE/naif0012.tls
- data/SPICE/pck00011.tpc # Mars frame definitions
constellation:
- path: ephemeris/MARS_SAT1.csv
- path: ephemeris/MARS_SAT2.csv
After updating the scenario file and placing the kernels in data/SPICE/, rerun
poetry run acons ingest pointing to the modified configuration. The ingest stage reuses the same
Python code regardless of the target body; all differences are encapsulated by the supplied SPICE
assets and ephemerides.
Vehicle ephemeris helper
For vehicle movement scripts, use the standalone loader in
src/vehicle_ephemeris_load.py. The ephemeris_vehicle_type helper now auto-detects format from
user.vehicle_trajectory_path:
*.euses the STKEphemerisTimePosVelparser.*.csvuses a 7-column parser forJD, x, y, z, vx, vy, vz, where the epoch is Julian Day, the frame isIAU_MARS, position is in metres, and velocity is in metres per second.*.geojson/*.jsonuses a GeoJSON parser forFeatureCollectioninputs indata/vehicle/.
Both formats are normalized into a pandas.DataFrame with t, x, y, z, vx, vy, vz
and metadata in Ephemeris.meta, so downstream code paths and outputs stay unchanged.
For moving-user simulation (user.type: rover / user.type: edl), the absolute epoch stored in
the vehicle trajectory file is treated as informational only. The simulator anchors the first
trajectory sample at the scenario start time and preserves only the elapsed timing between support
points, so a trajectory can be replayed against any scenario epoch defined in the YAML.
GeoJSON support handles both Point and LineString features, preserves all properties, and
adds geometry descriptors (geometry_type, point lon/lat/alt or line path metadata) in
meta["geojson_records"]. For LineStrings, coordinates are expanded to samples and timing is
derived from SCLK_START/SCLK_END when available (otherwise a monotonic fallback index is used).
Coordinates are converted to IAU_MARS Cartesian states and expressed in meters.
For STK inputs, the loader still validates CentralBody, ScenarioEpoch, and CoordinateSystem
headers, and get_states transforms between STK coordinate systems (Fixed/ICRF) and SPICE frames
(IAU_MARS/J2000) using spiceypy.sxform after kernels are furnished (LSK + PCK).
If an STK file also includes the comment header Epoch in JDate format: ..., the loader records
that value and compares it against ScenarioEpoch. When the two epochs disagree, the JDate epoch is
preferred as the time origin and a warning is logged. This prevents incorrect body-fixed longitudes
when an STK export contains contradictory epoch metadata in its header.
For visualization, use scripts/plot_vehicle_mars_trajectory.py:
python scripts/plot_vehicle_mars_trajectory.py --ephem MartianRover_real.e --kernels data/SPICE
python scripts/plot_vehicle_mars_trajectory.py --ephem data/vehicle/MartianRover_real.e --kernels data/SPICE --animate
The script writes:
trajectory_2d.png(longitude/latitude in Mars-fixed frame)trajectory_3d.png(trajectory relative to Mars)trajectory_3d_zoomed.png(trajectory-relative zoomed 3D view)trajectory_2d.mp4(or.giffallback) when--animateis enabledtrajectory_3d.mp4(or.giffallback) when--animateis enabled; animation view auto-zooms to trajectory boundstrajectory_concept_check.md(declared vs inferred frame/time/units + recommended header contract)
The 2D animation title shows only the UTC time span and omits source/target frame text for cleaner playback captions.
Static 2D/3D figures also omit source/target frame names in titles and keep only the UTC span.
For GeoJSON inputs, scripts/plot_vehicle_mars_trajectory.py now generates a 2D ground-track
animation (trajectory_2d.mp4 or .gif fallback) by default, even when --animate is not set.
To avoid ambiguous transforms, prefer explicit ephemeris header fields:
FRAME=<SPICE frame name or STK CoordinateSystem>CENTER=MARS(orCentralBody Mars)TIME_SYSTEM=UTC|ET|TDBTIME_FORMAT=SECONDS_PAST_SCENARIO_EPOCH|SECONDS_PAST_J2000POSITION_UNITS=m|kmVELOCITY_UNITS=m/s|km/s
If frame, time system/format, kernel support, or units cannot be determined, the script fails with an actionable error.