///|
pub struct TleRecord {
name : String
satellite_number : Int
classification : Char
epoch_year : Int
epoch_day : Double
inclination_rad : Double
raan_rad : Double
eccentricity : Double
arg_periapsis_rad : Double
mean_anomaly_rad : Double
mean_motion_rev_day : Double
revolution_number : Int
} derive(Debug, Eq)
///|
pub(all) enum TleParseError {
LineTooShort(String)
InvalidNumber(String)
InvalidFormat(String)
} derive(Debug, Eq)
///|
fn slice_text(s : String, start : Int, end : Int) -> String {
s[start:end].trim().to_owned()
}
///|
fn digit_value(c : Char) -> Int? {
match c {
'0' => Some(0)
'1' => Some(1)
'2' => Some(2)
'3' => Some(3)
'4' => Some(4)
'5' => Some(5)
'6' => Some(6)
'7' => Some(7)
'8' => Some(8)
'9' => Some(9)
_ => None
}
}
///|
fn parse_int_field(s : String) -> Result[Int, TleParseError] {
let text = s.trim()
let mut sign = 1
let mut value = 0
let mut seen_digit = false
for index, c in text {
if index == 0 && c == '-' {
sign = -1
} else if index == 0 && c == '+' {
()
} else {
match digit_value(c) {
Some(d) => {
seen_digit = true
value = value * 10 + d
}
None => return Err(TleParseError::InvalidNumber(text.to_owned()))
}
}
}
if seen_digit {
Ok(sign * value)
} else {
Err(TleParseError::InvalidNumber(text.to_owned()))
}
}
///|
fn parse_double_field(s : String) -> Result[Double, TleParseError] {
let text = s.trim()
let mut sign = 1.0
let mut integer = 0.0
let mut fraction = 0.0
let mut scale = 1.0
let mut after_dot = false
let mut seen_digit = false
for index, c in text {
if index == 0 && c == '-' {
sign = -1.0
} else if index == 0 && c == '+' {
()
} else if c == '.' {
if after_dot {
return Err(TleParseError::InvalidNumber(text.to_owned()))
}
after_dot = true
} else {
match digit_value(c) {
Some(d) => {
seen_digit = true
if after_dot {
scale = scale * 10.0
fraction = fraction + Double::from_int(d) / scale
} else {
integer = integer * 10.0 + Double::from_int(d)
}
}
None => return Err(TleParseError::InvalidNumber(text.to_owned()))
}
}
}
if seen_digit {
Ok(sign * (integer + fraction))
} else {
Err(TleParseError::InvalidNumber(text.to_owned()))
}
}
///|
pub fn parse_tle(
name : String,
line1 : String,
line2 : String,
) -> Result[TleRecord, TleParseError] {
if line1.length() < 63 {
return Err(TleParseError::LineTooShort("line1"))
}
if line2.length() < 63 {
return Err(TleParseError::LineTooShort("line2"))
}
if !(line1 is [.. "1 ", ..]) || !(line2 is [.. "2 ", ..]) {
return Err(
TleParseError::InvalidFormat("TLE lines must start with 1 and 2"),
)
}
let sat1 = match parse_int_field(slice_text(line1, 2, 7)) {
Ok(v) => v
Err(e) => return Err(e)
}
let sat2 = match parse_int_field(slice_text(line2, 2, 7)) {
Ok(v) => v
Err(e) => return Err(e)
}
if sat1 != sat2 {
return Err(TleParseError::InvalidFormat("satellite numbers differ"))
}
let classification = line1.get_char(7).unwrap_or('U')
let epoch_year = match parse_int_field(slice_text(line1, 18, 20)) {
Ok(v) => v
Err(e) => return Err(e)
}
let epoch_day = match parse_double_field(slice_text(line1, 20, 32)) {
Ok(v) => v
Err(e) => return Err(e)
}
let ecc_text = slice_text(line2, 26, 33)
let eccentricity = match parse_double_field("0.\{ecc_text}") {
Ok(v) => v
Err(e) => return Err(e)
}
let inclination = match parse_double_field(slice_text(line2, 8, 16)) {
Ok(v) => v
Err(e) => return Err(e)
}
let raan = match parse_double_field(slice_text(line2, 17, 25)) {
Ok(v) => v
Err(e) => return Err(e)
}
let arg_periapsis = match parse_double_field(slice_text(line2, 34, 42)) {
Ok(v) => v
Err(e) => return Err(e)
}
let mean_anomaly = match parse_double_field(slice_text(line2, 43, 51)) {
Ok(v) => v
Err(e) => return Err(e)
}
let mean_motion = match parse_double_field(slice_text(line2, 52, 63)) {
Ok(v) => v
Err(e) => return Err(e)
}
let revolution = if line2.length() >= 68 {
match parse_int_field(slice_text(line2, 63, 68)) {
Ok(v) => v
Err(e) => return Err(e)
}
} else {
0
}
Ok({
name,
satellite_number: sat1,
classification,
epoch_year,
epoch_day,
inclination_rad: radians(inclination),
raan_rad: radians(raan),
eccentricity,
arg_periapsis_rad: radians(arg_periapsis),
mean_anomaly_rad: radians(mean_anomaly),
mean_motion_rev_day: mean_motion,
revolution_number: revolution,
})
}
///|
pub fn mean_motion_rev_day_to_rad_s(mean_motion_rev_day : Double) -> Double {
mean_motion_rev_day * two_pi / 86400.0
}
///|
pub fn semi_major_axis_from_mean_motion(
mu : Double,
mean_motion_rev_day : Double,
) -> Double {
let n = mean_motion_rev_day_to_rad_s(mean_motion_rev_day)
@math.pow(mu / (n * n), 1.0 / 3.0)
}
///|
pub fn tle_to_mean_elements(mu : Double, tle : TleRecord) -> ClassicalElements {
let a = semi_major_axis_from_mean_motion(mu, tle.mean_motion_rev_day)
let eccentric = solve_kepler(tle.mean_anomaly_rad, tle.eccentricity)
ClassicalElements::new(
a,
tle.eccentricity,
tle.inclination_rad,
tle.raan_rad,
tle.arg_periapsis_rad,
true_anomaly_from_eccentric(eccentric, tle.eccentricity),
)
}
///|
pub struct Sgp4Status {
available : Bool
note : String
} derive(Debug, Eq)
///|
pub fn sgp4_status() -> Sgp4Status {
{
available: false,
note: "TLE parsing and mean-element conversion are available; full SGP4 force model is intentionally reserved for a later compatible package.",
}
}