use qtty::angular::Degrees;
use qtty::angular::Turn;
use qtty::angular_rate::AngularRate;
use qtty::time::Day;
use tempoch::{Time, UTC};
use super::TleError;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct SatelliteNumber(pub u32);
impl SatelliteNumber {
pub fn parse(field: &str) -> Result<Self, TleError> {
let s = field.trim_start();
if s.is_empty() || s.len() > 5 {
return Err(TleError::InvalidAlpha5 { raw: field.into() });
}
let bytes = s.as_bytes();
let first = bytes[0];
if first.is_ascii_digit() {
let n: u32 = s.parse().map_err(|_| TleError::InvalidNumber {
field: "satellite_number",
raw: field.into(),
})?;
return Ok(SatelliteNumber(n));
}
if !first.is_ascii_uppercase() || first == b'I' || first == b'O' {
return Err(TleError::InvalidAlpha5 { raw: field.into() });
}
let value = match first {
b'A'..=b'H' => first - b'A' + 10,
b'J'..=b'N' => first - b'J' + 18,
b'P'..=b'Z' => first - b'P' + 23,
_ => return Err(TleError::InvalidAlpha5 { raw: field.into() }),
} as u32;
let tail = &s[1..];
if tail.len() != 4 || !tail.bytes().all(|b| b.is_ascii_digit()) {
return Err(TleError::InvalidAlpha5 { raw: field.into() });
}
let tail_n: u32 = tail
.parse()
.map_err(|_| TleError::InvalidAlpha5 { raw: field.into() })?;
Ok(SatelliteNumber(value * 10_000 + tail_n))
}
pub fn format_alpha5(self) -> Result<String, TleError> {
let n = self.0;
if n < 100_000 {
return Ok(format!("{n:05}"));
}
if n >= 340_000 {
return Err(TleError::InvalidAlpha5 {
raw: format!("{n}"),
});
}
let high = (n / 10_000) as u8;
let tail = n % 10_000;
let letter = match high {
10..=17 => b'A' + (high - 10),
18..=22 => b'J' + (high - 18),
23..=33 => b'P' + (high - 23),
_ => unreachable!(),
};
Ok(format!("{}{:04}", letter as char, tail))
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Classification {
Unclassified,
Classified,
Secret,
}
impl Classification {
pub fn from_char(c: char) -> Result<Self, TleError> {
match c {
'U' => Ok(Self::Unclassified),
'C' => Ok(Self::Classified),
'S' => Ok(Self::Secret),
_ => Err(TleError::InvalidClassification { raw: c }),
}
}
pub fn as_char(self) -> char {
match self {
Self::Unclassified => 'U',
Self::Classified => 'C',
Self::Secret => 'S',
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct InternationalDesignator(pub String);
#[derive(Clone, Debug)]
pub struct TLE {
pub name: Option<String>,
pub norad_id: SatelliteNumber,
pub classification: Classification,
pub international_designator: InternationalDesignator,
pub epoch: Time<UTC>,
pub mean_motion_dot: f64,
pub mean_motion_ddot: f64,
pub bstar: f64,
pub element_set_number: u16,
pub revolution_number_at_epoch: u32,
pub inclination: Degrees,
pub raan: Degrees,
pub eccentricity: f64,
pub argument_of_perigee: Degrees,
pub mean_anomaly: Degrees,
pub mean_motion: AngularRate<Turn, Day>,
}