Skip to content

Orbit Determination

This tutorial explains how to run tracking data through a scalar orbit determination filter to estimate a spacecraft's position, velocity, and coefficient of reflectivity.

Executing an Orbit Determination Filter

from nyx_space import ExportCfg, Spacecraft
from nyx_space.anise import MetaAlmanac
from nyx_space.anise.analysis import OrbitalElement
from nyx_space.anise.astro import Orbit
from nyx_space.anise.constants import CelestialObjects, Frames
from nyx_space.mission_design import (
    AccelModels,
    Dynamics,
    GravityFieldConfig,
    PointMasses,
    Propagator,
)
from nyx_space.monte_carlo import StateDispersion, StateParameter
from nyx_space.orbit_determination import (
    GroundStation,
    GroundTrackingArcSim,
    Handoff,
    KalmanVariant,
    LocalFrame,
    Location,
    MeasurementType,
    ProcessNoise,
    Scheduler,
    SigmaRejection,
    SpacecraftEstimate,
    SpacecraftODProcess,
    StochasticNoise,
    TrkConfig,
)
from nyx_space.time import Epoch, Unit

almanac = MetaAlmanac.latest()
eme2k = almanac.frame_info(Frames.EME2000)

# Step 1: Build a reference trajectory
orbit = Orbit(
    5442.1625926801835,
    -4068.9498468206248,
    -13.456851447751518,
    2.8581975428173836,
    3.8097859312745794,
    6.002126693122689,
    Epoch("2025-08-25 11:55:44 UTC"),
    eme2k,
)
spacecraft = Spacecraft(orbit)

# Step 2: Disperse this spacecraft state to create an initial filter error
disp = [
    StateDispersion.zero_mean(
        StateParameter.Element(OrbitalElement.SemiMajorAxis), 1.0
    ),
    StateDispersion.zero_mean(
        StateParameter.Element(OrbitalElement.Eccentricity), 1e-6
    ),
]

# Builds a zero mean estimate from these dispersions
estimate = SpacecraftEstimate.from_dispersions(
    nominal_state=spacecraft, dispersions=disp, seed=123
)

# Propagate to generate the ground-truth trajectory.
accel_models = AccelModels(
    point_masses=PointMasses(
        celestial_objects=[CelestialObjects.MOON, CelestialObjects.SUN]
    ),
    gravity_field=GravityFieldConfig(
        degree=10,
        order=10,
        filepath="../data/01_planetary/EGM2008_to2190_TideFree.gz",
        frame=Frames.IAU_EARTH_FRAME.to_frameuid(),
    ),
)
dynamics = Dynamics(accel_models)
propagator = Propagator(dynamics, almanac)
traj = propagator.for_duration(spacecraft, Unit.Day * 1, trajectory=True).trajectory

# Step 3: Configure Stochastic Measurement Noise, generate tracking data.
stochastic_noises = {
    MeasurementType.Range: StochasticNoise(name="Range"),
    MeasurementType.Doppler: StochasticNoise(name="Doppler"),
}

# Step 4: Build the ground station network
gs0 = GroundStation(
    name="Paris, FR",
    location=Location(
        latitude_deg=48.8566,
        longitude_deg=2.3522,
        height_km=0.4,
        frame=Frames.IAU_EARTH_FRAME.to_frameuid(),
        terrain_mask=[],
        terrain_mask_ignored=True,
    ),
    stochastic_noises=stochastic_noises,
)

gs1 = GroundStation(
    "Denver, CO",
    Location(
        39.7420, -104.9915, 1.8, Frames.IAU_EARTH_FRAME.to_frameuid(), [], True
    ),
    stochastic_noises,
)

# Step 5: Define Tracking Schedules
configs = {
    "Paris, FR": TrkConfig(Scheduler(Handoff.Greedy)),
    "Denver, CO": TrkConfig(Scheduler(Handoff.Greedy)),
}

network = {"Paris, FR": gs0, "Denver, CO": gs1}
trk_sim = GroundTrackingArcSim(network, traj, configs)
trk_sim.build_schedule(almanac)

# Step 6: Generate synthetic measurements
trk_arc = trk_sim.generate_measurements(almanac)

# Step 7: Run the orbit determination filter
od_proc_deviation = SpacecraftODProcess(
    propagator, KalmanVariant.DeviationTracking, network
)
od_dev_sol = od_proc_deviation.process_arc(estimate, trk_arc)

# Export the whole orbit determination solution into a single Parquet file.
od_dev_sol.to_parquet("od_dev.pq", ExportCfg(False))

# Step 8: Running the smoother
smoothed = od_dev_sol.smooth(almanac)

# Reference update algorithm setup
process_noise = ProcessNoise.from_velocity_m_s(
    vx_m_s=1e-6,
    vy_m_s=1e-6,
    vz_m_s=1e-6,
    noise_duration=Unit.Second * 1,
    disable_time=Unit.Hour * 2,
    local_frame=LocalFrame.Inertial,
)
od_proc = SpacecraftODProcess(
    propagator, KalmanVariant.ReferenceUpdate, network, process_noise=process_noise
)
od_sol = od_proc.process_arc(estimate, trk_arc)
od_sol.to_parquet("od_ref_update.pq", ExportCfg(False))

# Export definitive ephemeris to OEM
definitive_ephem = od_sol.to_ephemeris("Test OD Spacecraft")
oem_filepath = "definitive_ephem.oem"
definitive_ephem.write_ccsds_oem(oem_filepath)