sidereon-core 1.4.1

Numerical astrodynamics propagation core plus the GNSS domain layer (SP3, broadcast ephemeris, multi-GNSS positioning, RTK/PPP, ionosphere/troposphere, DOP) behind a default-on gnss feature
Documentation
use std::collections::BTreeMap;

use sidereon_core::ephemeris::Sp3;
use sidereon_core::positioning::{
    Corrections, KlobucharCoeffs, Observation, SolveInputs, SurfaceMet,
};
use sidereon_core::static_positioning::{solve_static, StaticEpoch, StaticSolveOptions};
use sidereon_core::{GnssSatelliteId, GnssSystem};

fn fixture_sp3() -> Sp3 {
    let path = concat!(
        env!("CARGO_MANIFEST_DIR"),
        "/tests/fixtures/sp3/trimmed_go_static.sp3"
    );
    let bytes = std::fs::read(path).expect("read SP3 fixture");
    Sp3::parse(&bytes).expect("parse SP3 fixture")
}

fn gps(prn: u8) -> GnssSatelliteId {
    GnssSatelliteId::new(GnssSystem::Gps, prn).expect("valid GPS PRN")
}

fn go_fixture_inputs() -> SolveInputs {
    let observations = [
        (8, 23_825_519.844459895),
        (10, 22_717_690.10174763),
        (16, 20_478_653.376262885),
        (18, 21_768_335.23365917),
        (20, 21_248_327.738292538),
        (21, 20_808_709.800933376),
        (26, 21_126_481.58786735),
        (27, 21_341_367.541037586),
    ]
    .into_iter()
    .map(|(prn, pseudorange_m)| Observation {
        satellite_id: gps(prn),
        pseudorange_m,
    })
    .collect();
    SolveInputs {
        observations,
        t_rx_j2000_s: 646_272_000.0,
        t_rx_second_of_day_s: 43_200.0,
        day_of_year: 176.5,
        initial_guess: [4.5e6, 0.5e6, 4.5e6, 0.0],
        corrections: Corrections::NONE,
        klobuchar: KlobucharCoeffs {
            alpha: [0.0; 4],
            beta: [0.0; 4],
        },
        beidou_klobuchar: None,
        galileo_nequick: None,
        sbas_iono: None,
        glonass_channels: BTreeMap::new(),
        met: SurfaceMet::default(),
        robust: None,
    }
}

fn bit_pattern(values: &[f64]) -> Vec<u64> {
    values.iter().map(|value| value.to_bits()).collect()
}

#[test]
fn go_fixture_static_portable_bits() {
    let source = fixture_sp3();
    let inputs = go_fixture_inputs();
    let first = StaticEpoch::from_solve_inputs(inputs.clone());
    let second = StaticEpoch::from_solve_inputs(inputs);
    let static_result = solve_static(&source, &[first, second], StaticSolveOptions::default())
        .expect("static solve");

    assert_eq!(
        bit_pattern(&static_result.position.as_array()),
        vec![0x41511b07ff6d7461, 0x4120cd6b5f0f3fb6, 0x41511e622290ed5b,]
    );
    assert_eq!(
        static_result
            .per_epoch_clock
            .iter()
            .map(|clock| clock.clock_s.to_bits())
            .collect::<Vec<_>>(),
        vec![0x3f1a3b88234bcff9, 0x3f1a3b88234bcff9]
    );
    let ecef = static_result
        .covariance
        .position_ecef_m2
        .into_iter()
        .flatten()
        .collect::<Vec<_>>();
    assert_eq!(
        bit_pattern(&ecef),
        vec![
            0x401782d26e36cfd9,
            0x3faa3d846f455d90,
            0x4004fb98e648c404,
            0x3faa3d846f455d90,
            0x3ff6b38b995266fb,
            0x3febe4866fbce509,
            0x4004fb98e648c404,
            0x3febe4866fbce509,
            0x4008045be20a8ea6,
        ]
    );
    let state = static_result
        .covariance
        .state_m2
        .iter()
        .flatten()
        .copied()
        .collect::<Vec<_>>();
    assert_eq!(
        bit_pattern(&state),
        vec![
            0x401782d26e36cfd9,
            0x3faa3d846f455d90,
            0x4004fb98e648c404,
            0x40150690b6fe685a,
            0x40150690b6fe685a,
            0x3faa3d846f455d90,
            0x3ff6b38b995266fb,
            0x3febe4866fbce509,
            0x3fe31f309a58b4e9,
            0x3fe31f309a58b4e7,
            0x4004fb98e648c404,
            0x3febe4866fbce509,
            0x4008045be20a8ea6,
            0x4008f69cda22e0da,
            0x4008f69cda22e0da,
            0x40150690b6fe685a,
            0x3fe31f309a58b4e9,
            0x4008f69cda22e0da,
            0x40156e6be7e8618f,
            0x40144247fc8a61fe,
            0x40150690b6fe685a,
            0x3fe31f309a58b4e7,
            0x4008f69cda22e0da,
            0x40144247fc8a61fe,
            0x40156e6be7e8618f,
        ]
    );
    assert_eq!(static_result.metadata.iterations, 9);
    assert_eq!(
        static_result.geometry_quality.condition_number.to_bits(),
        0x402846c3b2c6388d
    );
    assert_eq!(
        static_result.geometry_quality.gdop.to_bits(),
        0x4012562a1c8a19f4
    );
    assert_eq!(
        static_result
            .residuals_m
            .iter()
            .map(|row| row.residual_m.to_bits())
            .collect::<Vec<_>>(),
        vec![
            0x3f40cd1000000000,
            0xbf378e0000000000,
            0xbf38c9e000000000,
            0x3f5161ec00000000,
            0x3f2a2b0000000000,
            0xbf30255000000000,
            0x3f3c5ed000000000,
            0xbefdad0000000000,
            0x3f40cd1000000000,
            0xbf378e0000000000,
            0xbf38c9e000000000,
            0x3f5161ec00000000,
            0x3f2a2b0000000000,
            0xbf30255000000000,
            0x3f3c5ed000000000,
            0xbefdad0000000000,
        ]
    );
    assert_eq!(
        static_result
            .residuals_m
            .iter()
            .map(|row| row.base_weight.to_bits())
            .collect::<Vec<_>>(),
        vec![
            0x3fb439f6b0724321,
            0x3fe9b0257691870c,
            0x3fe006d19c286312,
            0x3fb028d0c17f3f79,
            0x3fdd0ad788b89a4f,
            0x3fd93783778cfc91,
            0x3fec59af7f4c7937,
            0x3fcdbb1a1c321f3e,
            0x3fb439f6b0724321,
            0x3fe9b0257691870c,
            0x3fe006d19c286312,
            0x3fb028d0c17f3f79,
            0x3fdd0ad788b89a4f,
            0x3fd93783778cfc91,
            0x3fec59af7f4c7937,
            0x3fcdbb1a1c321f3e,
        ]
    );
}