use super::sgp4_lowlevel::sgp4_lowlevel; use super::sgp4init::sgp4init;
use crate::TimeLike;
use crate::mathtypes::DMatrix;
use thiserror::Error;
#[derive(Debug, Clone, Error, PartialEq, Eq, Copy )]
pub enum SGP4Error {
#[error("Success")]
SGP4Success = 0,
#[error("Eccentricity > 1 or < 0")]
SGP4ErrorEccen = 1,
#[error("Mean motion < 0")]
SGP4ErrorMeanMotion = 2,
#[error("Perturbed Eccentricity > 1 or < 0")]
SGP4ErrorPerturbEccen = 3,
#[error("Semi-Latus Rectum < 0")]
SGP4ErrorSemiLatusRectum = 4,
#[error("Unused")]
SGP4ErrorUnused = 5,
#[error("Orbit Decayed")]
SGP4ErrorOrbitDecay = 6,
}
impl From<i32> for SGP4Error {
fn from(val: i32) -> Self {
match val {
0 => Self::SGP4Success,
1 => Self::SGP4ErrorEccen,
2 => Self::SGP4ErrorMeanMotion,
3 => Self::SGP4ErrorPerturbEccen,
4 => Self::SGP4ErrorSemiLatusRectum,
6 => Self::SGP4ErrorOrbitDecay,
_ => Self::SGP4ErrorUnused,
}
}
}
impl From<SGP4Error> for i32 {
fn from(val: SGP4Error) -> Self {
match val {
SGP4Error::SGP4ErrorEccen => 1,
SGP4Error::SGP4ErrorMeanMotion => 2,
SGP4Error::SGP4ErrorOrbitDecay => 6,
SGP4Error::SGP4ErrorPerturbEccen => 3,
SGP4Error::SGP4ErrorSemiLatusRectum => 4,
SGP4Error::SGP4ErrorUnused => -1,
SGP4Error::SGP4Success => 0,
}
}
}
pub struct SGP4State {
pub pos: DMatrix<f64>,
pub vel: DMatrix<f64>,
pub errcode: Vec<SGP4Error>,
}
use super::{GravConst, OpsMode, SGP4Source};
#[inline]
pub fn sgp4<T: TimeLike>(sgp4source: &mut impl SGP4Source, tm: &[T]) -> anyhow::Result<SGP4State> {
sgp4_full(sgp4source, tm, GravConst::WGS84, OpsMode::IMPROVED)
}
pub fn sgp4_full<T: TimeLike>(
sgp4source: &mut impl SGP4Source,
tm: &[T],
gravconst: GravConst,
opsmode: OpsMode,
) -> anyhow::Result<SGP4State> {
if sgp4source.satrec_mut().is_none() {
let args = sgp4source.sgp4_init_args()?;
*sgp4source.satrec_mut() = Some(sgp4init(
gravconst,
opsmode,
"satno",
args.jdsatepoch - 2433281.5,
args.bstar,
args.ndot,
args.nddot,
args.ecco,
args.argpo,
args.inclo,
args.mo,
args.no,
args.nodeo,
).map_err(|e| anyhow::anyhow!("SGP4 init error: {}", e))?);
}
let epoch = sgp4source.epoch();
let s = sgp4source.satrec_mut().as_mut().expect("satrec initialized");
let n = tm.len();
let mut rarr = DMatrix::<f64>::zeros(3, n);
let mut varr = DMatrix::<f64>::zeros(3, n);
let mut earr = Vec::<SGP4Error>::with_capacity(n);
for (pos, thetime) in tm.iter().enumerate() {
let tsince = (thetime.as_instant() - epoch).as_days() * 1440.0;
match sgp4_lowlevel(s, tsince) {
Ok((r, v)) => {
for i in 0..3 {
rarr[(i, pos)] = r[i];
varr[(i, pos)] = v[i];
}
earr.push(SGP4Error::SGP4Success)
}
Err(e) => earr.push(e.into()),
}
}
Ok(SGP4State {
pos: rarr * 1.0e3,
vel: varr * 1.0e3,
errcode: earr,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tle::TLE;
use crate::utils::test;
use anyhow::{bail, Result};
use std::io::BufRead;
#[test]
fn testsgp4() {
let line1: &str = "1 26900U 01039A 06106.74503247 .00000045 00000-0 10000-3 0 8290";
let line2: &str =
"2 26900 0.0164 266.5378 0003319 86.1794 182.2590 1.00273847 16981 9300.";
let line0: &str = "0 INTELSAT 902";
let mut tle = TLE::load_3line(line0, line1, line2).unwrap();
let tm = tle.epoch;
let states = sgp4(&mut tle, &[tm]).unwrap();
assert!(states.errcode[0] == SGP4Error::SGP4Success);
}
#[test]
fn vallado_testvecs() -> Result<()> {
let testdir = test::get_testvec_dir().unwrap().join("sgp4");
if !testdir.is_dir() {
bail!(
"Required SGP4 test vectors directory: \"{}\" does not exist.
Clone test vectors from:
<https://storage.googleapis.com/satkit-testvecs/>
or using python script in satkit repo: `python/test/download_testvecs.py`
or set \"SATKIT_TESTVEC_ROOT\" to point to directory",
testdir.to_string_lossy()
);
}
let tlefile = testdir.join("SGP4-VER.TLE");
let f = match std::fs::File::open(&tlefile) {
Err(why) => bail!("Could not open {}: {}", tlefile.display(), why),
Ok(file) => file,
};
let buf = std::io::BufReader::new(f);
let lines: Vec<String> = buf
.lines()
.map(|l| {
let line = l.unwrap();
line.chars().take(69).collect()
})
.collect();
let tles = TLE::from_lines(&lines).unwrap();
assert!(tles.len() > 5);
for mut tle in tles {
let fname = format!("{:05}.e", tle.sat_num);
let fh = testdir.join(fname);
let ftle = match std::fs::File::open(&fh) {
Err(why) => bail!("Could not open {}: {}", fh.display(), why),
Ok(file) => file,
};
for line in std::io::BufReader::new(ftle).lines() {
let maxposerr = 1.0e-5;
let mut maxvelerr = 1.0e-5;
let testvec: Vec<f64> = line
.unwrap()
.split_whitespace()
.map(|x| x.parse().unwrap_or(-1.0))
.collect();
if testvec.len() < 7 {
continue;
}
if testvec[0] < 0.0 {
continue;
}
let tm = tle.epoch + crate::Duration::from_seconds(testvec[0]);
let states = sgp4_full(&mut tle, &[tm], GravConst::WGS72, OpsMode::AFSPC);
let states = match states {
Ok(s) => s,
Err(e) => {
if tle.sat_num == 33334 {
continue;
}
return Err(e);
}
};
if states.errcode[0] != SGP4Error::SGP4Success {
continue;
}
for idx in 0..3 {
if testvec[idx + 4].abs() < 1.0e-4 {
maxvelerr = 1.0e-4;
}
if testvec[idx + 4].abs() < 1.0e-6 {
maxvelerr = 1.0e-2;
}
let poserr =
(states.pos[(idx, 0)].mul_add(1.0e-3, -testvec[idx + 1]) / testvec[idx + 1]).abs();
let velerr =
(states.vel[(idx, 0)].mul_add(1.0e-3, -testvec[idx + 4]) / testvec[idx + 4]).abs();
assert!(poserr < maxposerr);
assert!(velerr < maxvelerr);
}
}
}
Ok(())
}
}