sidereon 0.13.0

Thin ergonomic API over sidereon-core: SP3 loading and SPP/RTK/PPP positioning solves with rich result structs and one error enum
Documentation

sidereon

GNSS and astrodynamics for Rust: propagate satellites, predict passes, solve precise positions (SPP / RTK / PPP), and convert between coordinate frames and time scales, checked against the references the field trusts (Vallado, Skyfield, IGS, IERS).

It's a pure-Rust engine, fast and #![forbid(unsafe_code)] at the surface, with one ergonomic crate that re-exports the whole stack. You just cargo add sidereon.

Install

cargo add sidereon

Quickstart: when does the ISS fly over you?

No data files, no setup: give it a two-line element set and a ground station, and ask when the satellite is above the horizon.

use std::time::{SystemTime, UNIX_EPOCH};

use sidereon::passes::{find_passes_for_satellite, GroundStation, PassFinderOptions, UtcInstant};
use sidereon::sgp4::Satellite;

fn main() {
    // Real ISS orbital elements (grab fresh ones from CelesTrak any time).
    let iss = Satellite::from_tle(
        "1 25544U 98067A   26178.50947090  .00006280  00000+0  12016-3 0  9996",
        "2 25544  51.6322 248.9966 0004278 238.4942 121.5629 15.49454046573359",
    )
    .expect("valid TLE");

    // A ground station: latitude, longitude in degrees, altitude in metres.
    let berkeley = GroundStation {
        latitude_deg: 37.87,
        longitude_deg: -122.27,
        altitude_m: 52.0,
    };

    // The next 24 hours, as UTC unix microseconds (the time unit everywhere here).
    let now_us = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .expect("clock after 1970")
        .as_micros() as i64;
    let start = UtcInstant::from_unix_microseconds(now_us);
    let end = UtcInstant::from_unix_microseconds(now_us + 24 * 3_600 * 1_000_000);

    // Every pass that climbs above 10 degrees.
    let options = PassFinderOptions {
        elevation_mask_deg: 10.0,
        ..PassFinderOptions::default()
    };
    let passes = find_passes_for_satellite(&iss, berkeley, start, end, options)
        .expect("valid pass-finder inputs");

    for pass in &passes {
        let secs = pass.aos.unix_microseconds() / 1_000_000;
        let (hh, mm) = ((secs % 86_400) / 3_600, (secs % 3_600) / 60);
        let minutes = (pass.los.unix_microseconds() - pass.aos.unix_microseconds()) as f64 / 60.0e6;
        println!("{hh:02}:{mm:02} UTC | {minutes:4.1} min | peak {:2.0} deg", pass.max_elevation_deg);
    }
}

A typical run prints something like:

08:30 UTC |  6.8 min | peak 88 deg
15:01 UTC |  6.6 min | peak 56 deg
16:39 UTC |  3.5 min | peak 14 deg

Each [SatellitePass] gives you acquisition (aos), loss (los), culmination time, and peak elevation. The same sidereon::passes module has look_angle (azimuth / elevation / range to a satellite at an instant) and propagate_teme_arc for raw state vectors; Satellite from sidereon::sgp4 is the propagator behind all of it.

Precise positioning

The positioning engine is the other half of the library: feed it pseudoranges and a precise-ephemeris (SP3) product and it returns a least-squares fix.

use sidereon::positioning::{Corrections, Observation, SolveInputs, SolvePolicy};
use sidereon::{load_sp3, solve_spp, GnssSatelliteId, GnssSystem};

let sp3 = load_sp3(&std::fs::read("igs_product.sp3")?)?;

let inputs = SolveInputs {
    observations: vec![
        Observation { satellite_id: GnssSatelliteId::new(GnssSystem::Gps, 1)?, pseudorange_m: 21_000_123.4 },
        Observation { satellite_id: GnssSatelliteId::new(GnssSystem::Gps, 8)?, pseudorange_m: 22_517_889.1 },
        // ...more satellites
    ],
    t_rx_j2000_s: receive_epoch_j2000_s,
    corrections: Corrections::IONO_TROPO,
    // ...time-of-day / day-of-year, Klobuchar coefficients, surface met, initial guess
    ..spp_inputs
};

let fix = solve_spp(&sp3, &inputs, /* with_geodetic */ true, policy)?;
println!("{:?}", fix.position);    // ItrfPositionM: ECEF metres
println!("{:?}", fix.geodetic);    // Some(Wgs84Geodetic): lat / lon / height
println!("{:?}", fix.used_sats);   // the satellites that contributed

solve_rtk_float_with, solve_rtk_fixed_with, solve_ppp_float_with, and solve_ppp_fixed_with follow the same pattern: a typed config in, a result struct with ECEF/geodetic position, residuals, DOP, and status out. One [Error] enum unifies every product-parse and solve failure, and solve_spp_batch fans a fleet of epochs across a rayon pool, bit-identical to the serial path.

What's in the box

  • Orbits: SGP4/TLE and OMM, numerical propagation with atmospheric drag and decay/reentry prediction, Kepler and anomaly conversions, classical and equinoctial elements, passes, look angles
  • Frames, time & geodesy: TEME ↔ GCRS ↔ ITRS, GMST/GAST, geodetic ↔ ECEF, UTC/TT/TDB/UT1, EGM96 geoid, DTED terrain elevation
  • Bodies & almanac: Sun/Moon/planet apparent places (geocentric or topocentric RA/Dec), Sun and Moon rise/set, seasons, moon phases, eclipses, planetary transits, plus JPL SPK (DAF/.bsp) kernels
  • Observation geometry: angular separation and position angle, phase/beta/parallactic angles, sub-solar and sub-observer points, terminator, satellite visual magnitude
  • Positioning: SPP, RTK (float/fixed), PPP (float/fixed), DOP, velocity, robust fault detection and exclusion
  • GNSS data: SP3, RINEX (obs/nav/clock), CRINEX, ANTEX, broadcast ephemeris, Bias-SINEX / CODE DCB biases, source-agnostic ephemeris sampling
  • Corrections: SBAS, RTCM SSR and Galileo HAS orbit/clock/bias correction stores
  • Space situational awareness: conjunction/TCA screening, collision probability, CDM, covariance, relative motion (RIC/RTN/LVLH, Clohessy-Wiltshire)
  • RF: link budget (FSPL, EIRP, C/N0, antenna gain)

The product parsers, look-angle helpers, and propagation shortcuts live at the crate root (load_sp3, solve_spp, passes, sgp4, tle, tca, relative, almanac); the full astrodynamics tree is under sidereon::astro. Lower-level RTK/PPP internals stay behind the explicit sidereon::raw escape hatch so the ergonomic surface stays small.

Other languages

sidereon is one validated engine with first-class interfaces in Rust, Python, C, Elixir, and WebAssembly: same numbers everywhere. See the live demo and docs at sidereon.dev.

How it's validated

The SGP4 propagator is a Rust port of David Vallado's reference implementation, bit-exact to it. Frames and time are checked against Skyfield and IERS; the positioning stack is checked against IGS products.

MIT licensed. The engine's SGP4 propagation credits David Vallado (AIAA 2006); see the sidereon-core crate for full attribution.