///|
fn tle_error(
kind : SatErrorKind,
line : Int,
fragment : StringView,
message : String,
) -> SatError {
SatError::new(kind, message, fragment=fragment.to_owned(), line~)
}
///|
fn parse_tle_decimal(text : StringView) -> Int? {
if text.is_empty() {
return None
}
let mut value = 0
let mut valid = true
text
.iter()
.each(char => {
if char >= '0' && char <= '9' {
value = value * 10 + (char.to_int() - '0'.to_int())
} else {
valid = false
}
})
if valid {
Some(value)
} else {
None
}
}
///|
fn split_fields(line : StringView) -> Array[StringView] {
line.trim().split(" ").filter(part => !part.is_empty()).to_array()
}
///|
fn parse_catalog_number(token : StringView) -> Int? {
if token.is_empty() {
return None
}
let mut value = 0
let mut seen_digit = false
let mut finished = false
let mut valid = true
token
.iter()
.each(char => {
if finished {
()
} else if char >= '0' && char <= '9' {
seen_digit = true
value = value * 10 + (char.to_int() - '0'.to_int())
} else if seen_digit {
finished = true
} else {
valid = false
}
})
if valid && seen_digit {
Some(value)
} else {
None
}
}
///|
fn checksum_digit(line : StringView) -> Int? {
let len = line.length()
let mut index = 0
let mut result : Int? = None
line
.iter()
.each(char => {
if index == len - 1 {
if char >= '0' && char <= '9' {
result = Some(char.to_int() - '0'.to_int())
} else {
result = None
}
}
index = index + 1
})
result
}
///|
fn checksum_value(line : StringView) -> Int {
let len = line.length()
let mut index = 0
let mut total = 0
line
.iter()
.each(char => {
if index < len - 1 {
if char >= '0' && char <= '9' {
total = total + (char.to_int() - '0'.to_int())
} else if char == '-' {
total = total + 1
}
}
index = index + 1
})
total % 10
}
///|
fn checksum_ok(line : StringView) -> Bool {
match checksum_digit(line) {
Some(expected) => checksum_value(line) == expected
None => false
}
}
///|
fn field_at(
fields : Array[StringView],
index : Int,
label : String,
) -> Result[StringView, SatError] {
if index < 0 || index >= fields.length() {
Err(tle_error(InvalidFormat, 0, label, "missing TLE field"))
} else {
Ok(fields[index])
}
}
///|
fn parse_core_tle(line1 : String, line2 : String) -> Result[Tle, SatError] {
let fields1 = split_fields(line1)
let fields2 = split_fields(line2)
if fields1.length() < 9 || fields2.length() < 8 {
return Err(
tle_error(
InvalidFormat,
0,
line1,
"TLE lines do not contain enough fields",
),
)
}
if fields1[0] != "1" || fields2[0] != "2" {
return Err(
tle_error(InvalidFormat, 0, line1, "TLE lines must begin with 1 and 2"),
)
}
let catalog1 = match parse_catalog_number(fields1[1]) {
Some(value) => value
None =>
return Err(
tle_error(InvalidCatalogNumber, 1, fields1[1], "invalid catalog number"),
)
}
let catalog2 = match parse_tle_decimal(fields2[1]) {
Some(value) => value
None =>
return Err(
tle_error(InvalidCatalogNumber, 2, fields2[1], "invalid catalog number"),
)
}
if catalog1 != catalog2 {
return Err(
tle_error(
MismatchedCatalogNumber,
2,
fields2[1],
"catalog numbers do not match",
),
)
}
let mean_motion_index = if fields2.length() > 7 {
7
} else {
fields2.length() - 1
}
let epoch = match field_at(fields1, 3, "epoch") {
Ok(value) => value.to_owned()
Err(error) => return Err(error)
}
let inclination = match field_at(fields2, 2, "inclination") {
Ok(value) => value.to_owned()
Err(error) => return Err(error)
}
let right_ascension = match field_at(fields2, 3, "right_ascension") {
Ok(value) => value.to_owned()
Err(error) => return Err(error)
}
let eccentricity = match field_at(fields2, 4, "eccentricity") {
Ok(value) => value.to_owned()
Err(error) => return Err(error)
}
let argument_of_perigee = match field_at(fields2, 5, "argument_of_perigee") {
Ok(value) => value.to_owned()
Err(error) => return Err(error)
}
let mean_anomaly = match field_at(fields2, 6, "mean_anomaly") {
Ok(value) => value.to_owned()
Err(error) => return Err(error)
}
let mean_motion = match field_at(fields2, mean_motion_index, "mean_motion") {
Ok(value) => value.to_owned()
Err(error) => return Err(error)
}
let mean_motion_first_derivative = match
field_at(fields1, 4, "mean_motion_first_derivative") {
Ok(value) => value.to_owned()
Err(error) => return Err(error)
}
let mean_motion_second_derivative = match
field_at(fields1, 5, "mean_motion_second_derivative") {
Ok(value) => value.to_owned()
Err(error) => return Err(error)
}
let bstar = match field_at(fields1, 6, "bstar") {
Ok(value) => value.to_owned()
Err(error) => return Err(error)
}
let line1_ok = checksum_ok(line1)
let line2_ok = checksum_ok(line2)
Ok({
line1,
line2,
catalog_number: catalog1,
epoch,
inclination,
right_ascension,
eccentricity,
argument_of_perigee,
mean_anomaly,
mean_motion,
mean_motion_first_derivative,
mean_motion_second_derivative,
bstar,
checksum_line1_ok: line1_ok,
checksum_line2_ok: line2_ok,
})
}
///|
/// Parse a two-line element set and preserve the raw orbital fields.
pub fn parse_tle(line1 : String, line2 : String) -> Result[Tle, SatError] {
parse_core_tle(line1, line2)
}
///|
/// Parse and require both TLE checksum digits to be valid.
pub fn validate_tle(line1 : String, line2 : String) -> Result[Tle, SatError] {
let tle = match parse_core_tle(line1, line2) {
Ok(value) => value
Err(error) => return Err(error)
}
if !tle.checksum_line1_ok {
return Err(
tle_error(InvalidChecksum, 1, tle.line1, "line 1 checksum mismatch"),
)
}
if !tle.checksum_line2_ok {
return Err(
tle_error(InvalidChecksum, 2, tle.line2, "line 2 checksum mismatch"),
)
}
Ok(tle)
}