1use serde::{Deserialize, Serialize};
5use std::convert::From;
6use std::fmt::{Display, Formatter, Result};
7use std::fs;
8use std::io::{BufWriter, Read, Write};
9
10use hifitime::prelude::*;
11
12#[derive(Clone, Debug, Serialize, Deserialize)]
14pub struct TLE {
15 pub name: String,
16 pub catalog_number: String,
17 pub classification: String,
18 pub international_designator: String,
19 pub epoch: Epoch,
20 pub mean_motion_1: f64,
21 pub mean_motion_2: f64,
22 pub radiation_pressure: f64,
23 pub ephemeris_type: u8,
24 pub element_set_number: u64,
25 pub inc: f64,
26 pub raan: f64,
27 pub eccentricity: f64,
28 pub arg_perigee: f64,
29 pub mean_anomaly: f64,
30 pub mean_motion: f64,
31 pub rev_num: u32,
32}
33
34impl From<&str> for TLE {
36 fn from(tle_str: &str) -> TLE {
38 return parse(tle_str);
39 }
40}
41
42pub fn write_json(tle: &TLE, path_str: &String) {
53 let file: fs::File = fs::File::create(path_str).expect("Unable to create file");
54 let mut writer: BufWriter<fs::File> = BufWriter::new(file);
55 serde_json::to_writer(&mut writer, tle).unwrap();
56 writer.flush().unwrap();
57}
58
59pub fn tles_from_file(path: &str) -> Vec<TLE> {
61 if path.contains(".json") {
62 return vec![read_json(path)];
63 } else {
64 return read_txt(path);
65 }
66}
67
68
69pub fn read_json(json_path: &str) -> TLE {
80 let mut file: fs::File =
81 fs::File::open(json_path).expect(format!("{json_path} could not be openned").as_str());
82
83 let mut data: String = String::new();
84 file.read_to_string(&mut data)
85 .expect(format!("{json_path} could not be read").as_str());
86
87 let tle_values: TLE = serde_json::from_str(&data).expect("JSON was not well-formatted");
89 return tle_values;
90}
91
92impl Display for TLE {
94 fn fmt(&self, formatter: &mut Formatter<'_>) -> Result {
95 write!(
96 formatter,
97 "{}\nCatalog #: {}\nClassification: {}\nIntl Desig: {}\nSet #: {}\nEpoch: {}\nMean Motion: {}\nMean Motion prime: {}\nMean Motion prime 2: {}\nRadiation Pressure: {}\nInclination: {}\nRaan: {}\nEccentricity: {}\nArgument of Perigee: {}\nMean Anomaly: {}\nRevolution #: {}",
98 self.name,
99 self.catalog_number,
100 self.classification,
101 self.international_designator,
102 self.element_set_number,
103 self.epoch,
104 self.mean_motion,
105 self.mean_motion_1,
106 self.mean_motion_2,
107 self.radiation_pressure,
108 self.inc,
109 self.raan,
110 self.eccentricity,
111 self.arg_perigee,
112 self.mean_anomaly,
113 self.rev_num
114 )
115 }
116}
117
118pub fn parse(tle_str: &str) -> TLE {
130 let lines: Vec<&str> = tle_str.lines().collect();
131 let n_lines: usize = lines.len();
132
133 let (idx_1, idx_2) = match n_lines {
134 3 => (1, 2),
135 2 => (0, 1),
136 _ => panic!("Invalid number of lines"),
137 };
138
139 let line_1: String = lines[idx_1].trim().to_string();
140 validate_checksum(&line_1);
141
142 let catalog_number: &str = &line_1[2..=6];
143
144 let classification: &str = &line_1[7..=7];
145
146 let intnl_desig: &str = &line_1[9..=16];
147
148 let name: &str;
150 if lines.len() == 3 {
151 name = lines[0];
152 } else {
153 name = intnl_desig
154 }
155
156 let epoch_str: &str = &line_1[18..=31];
157
158 let year_endian: i32 = epoch_str[0..=1]
159 .to_string()
160 .parse::<i32>()
161 .expect("Unable to parse year_endian value at epoch_str[0..=1]");
162
163 let epoch_year: i32;
165 if year_endian < 57 {
166 epoch_year = 2000 + year_endian;
167 } else {
168 epoch_year = 1900 + year_endian;
169 }
170
171 let binding: String = epoch_str[2..].to_string();
172 let epoch_day_full: Vec<&str> = binding.split_terminator('.').collect();
173 let day_of_year: u32 = epoch_day_full[0]
174 .to_string()
175 .parse::<u32>()
176 .expect("Unable to parse day_of_year value at epoch_day_full[0]");
177
178 let month_day: (u8, u8) = calc_month_day(day_of_year, epoch_year as u32);
179
180 let percent_of_day: f64 = (".".to_owned() + epoch_day_full[1])
181 .parse::<f64>()
182 .expect("Unable to parse percent_of_day value at epoch_day_full[1]");
183
184 let hours_dec: f64 = percent_of_day * 24.0;
185
186 let hours_whole: u8 = hours_dec.div_euclid(24.0).floor() as u8;
188 let hours_part: f64 = hours_dec.rem_euclid(24.0);
189 let minutes_dec: f64 = hours_part * 60.;
190
191 let minutes_whole: u8 = minutes_dec.div_euclid(60.).floor() as u8;
193 let minutes_part: f64 = minutes_dec.rem_euclid(60.);
194 let seconds_dec: f64 = minutes_part * 60.;
195
196 let seconds_whole: u8 = seconds_dec.div_euclid(60.).floor() as u8;
198
199 let full_epoch: Epoch = Epoch::from_gregorian_hms(
201 epoch_year as i32,
202 month_day.0,
203 month_day.1,
204 hours_whole,
205 minutes_whole,
206 seconds_whole,
207 TimeScale::UTC,
208 );
209
210 let mean_motion_1_sign: f64 = (line_1[33..=33].to_string() + "1")
212 .trim()
213 .parse::<f64>()
214 .expect("Unable to parse mean_motion_1_sign value at line_1[33..=33]");
215
216 let mean_motion_1_base: f64 = line_1[34..=42]
217 .to_string()
218 .parse::<f64>()
219 .expect("Unable to parse mean_motion_1_base value at line_1[34..=42]");
220 let mean_motion_1: f64 = mean_motion_1_base * mean_motion_1_sign;
221
222 let mean_mot_2_sign: f64 = (line_1[44..=44].to_string() + "1")
224 .trim()
225 .parse::<f64>()
226 .expect("Invalid mean motion 2 sign");
227 let mean_mot_2_base: f64 = line_1[45..=49].to_string().parse::<f64>().unwrap();
228 let mean_mot_2_exp = line_1[50..=51].to_string().parse::<f64>().unwrap();
229 let mean_mot_2_pow: f64 = 10_f64.powf(mean_mot_2_exp);
230 let mean_motion_2: f64 = (mean_mot_2_sign * mean_mot_2_base) * mean_mot_2_pow;
231
232 let rad_press_sign: f64 = (line_1[53..=53].to_string() + "1")
234 .trim()
235 .parse::<f64>()
236 .expect("Invalid radiation pressure sign");
237 let rad_press_base: f64 = line_1[54..=58].to_string().parse::<f64>().unwrap();
238 let rad_press_exp = line_1[59..=60].to_string().parse::<f64>().unwrap();
239 let rad_press_pow: f64 = 10_f64.powf(rad_press_exp);
240 let radiation_pressure: f64 = rad_press_sign * rad_press_base * rad_press_pow;
241
242 let ephemeris_type: u8 = line_1[62..=62]
243 .to_string()
244 .parse::<u8>()
245 .expect("Unable to parse ephemeris_type value at line_1[62..=62]");
246
247 let element_set_number: u64 = line_1[64..=67]
248 .to_string()
249 .trim()
250 .parse::<u64>()
251 .expect("Unable to parse element_set_number value at line_1[64..=67]");
252
253 let line2: String = lines[idx_2].trim().to_string();
254 validate_checksum(&line2);
255
256 let inc: f64 = line2[8..=15].to_string().trim().parse::<f64>().expect("Invalid inclination angle");
259
260 let raan: f64 = line2[17..=24].to_string().trim().parse::<f64>().expect("Invalid right angle of ascending node");
262
263 let eccentricity: f64 = (".".to_owned() + &line2[26..=32]).parse::<f64>().expect("Invalid eccenticity");
265
266 let arg_perigee: f64 = line2[34..=41].to_string().trim().parse::<f64>().expect("Invalid argument of perigee");
268
269 let mean_anomaly: f64 = line2[44..=50].to_string().parse::<f64>().expect("Invalid mean anomaly");
271
272 let mean_motion: f64 = line2[52..=62].to_string().trim().parse::<f64>().expect("Invalid mean motion");
274
275 let rev_num: u32 = line2[64..=68].to_string().trim().parse::<u32>().expect("Invalid revolution number");
277
278 let tle: TLE = TLE {
279 name: name.trim().to_string(),
280 catalog_number: catalog_number.trim().to_string(),
281 classification: classification.trim().to_string(),
282 international_designator: intnl_desig.trim().to_string(),
283 epoch: full_epoch,
284 mean_motion_1: mean_motion_1,
285 mean_motion_2: mean_motion_2,
286 radiation_pressure: radiation_pressure,
287 ephemeris_type: ephemeris_type,
288 element_set_number: element_set_number,
289 inc: inc,
290 raan: raan,
291 eccentricity: eccentricity,
292 arg_perigee: arg_perigee,
293 mean_anomaly: mean_anomaly,
294 mean_motion: mean_motion,
295 rev_num: rev_num,
296 };
297
298 return tle;
299}
300
301pub fn validate_checksum(line: &String) {
308 let mut checksum: u32 = 0;
309 for i_char in line.chars() {
310 if i_char == '-' {
311 checksum += 1;
312 } else if i_char != ' ' && i_char.is_numeric() {
313 checksum += i_char
314 .to_string()
315 .parse::<u32>()
316 .expect(format!("Unable to parse {} as u32", i_char).as_str());
317 }
318 }
319 let tle_checksum: u32 = line[68..=68]
320 .to_string()
321 .parse::<u32>()
322 .expect("Unable to parse checksum value");
323
324 let mod_10: u32 = (checksum - tle_checksum) % 10;
326
327 assert!(
328 mod_10 == tle_checksum,
329 "calculated = {}, tle value = {}",
330 mod_10,
331 tle_checksum
332 );
333}
334
335pub fn calc_month_day(day_of_year: u32, year: u32) -> (u8, u8) {
353 assert!(day_of_year < 366, "Day of year must be less than 366");
354
355 let feb_days: u32;
356 if check_if_leap_year(year) {
357 feb_days = 29;
358 } else {
359 feb_days = 28;
360 }
361
362 let month_lengths: Vec<u32> = vec![31, feb_days, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
363
364 let mut month: u8 = 1;
365 let mut sum_days: u32 = month_lengths[0];
366
367 while sum_days < day_of_year - month_lengths[month as usize - 1] {
368 month += 1;
369 sum_days += month_lengths[month as usize - 1];
370 }
371
372 let month: u8 = month;
373 let day: u32 = day_of_year - sum_days;
374
375 return (month, day as u8);
376}
377
378fn check_if_leap_year(year: u32) -> bool {
390 let rule1: bool = year % 4 == 0;
391 let rule2: bool = year % 100 != 0;
392 let rule3: bool = year % 400 == 0;
393 let is_leap_year: bool = rule1 && (rule2 || rule3);
394 return is_leap_year;
395}
396
397pub fn query_celestrak(query: &str, value: &str, verbose: bool) -> Vec<TLE> {
399 let url: String = "https://celestrak.org/NORAD/elements/gp.php?".to_owned()
400 + query
401 + "="
402 + value
403 + "&FORMAT=tle";
404
405 let mut response = reqwest::blocking::get(url).expect("Expected response");
406 let mut body = String::new();
407 response
408 .read_to_string(&mut body)
409 .expect("Unable to read request");
410
411 if verbose {
412 println!("\n Site Status: {}", response.status());
413 println!("\n Site Headers:\n{:#?}", response.headers());
414 println!("\n Site Body:\n{}", body);
415 }
416
417 return read_multi_tle(&body.as_str());
418}
419
420pub fn read_txt(path: &str) -> Vec<TLE> {
422 let contents: String = fs::read_to_string(path)
423 .expect(format!("Unable to read file:\n{}", path).as_str());
424
425 return read_multi_tle(contents.as_str());
426}
427
428pub fn read_multi_tle(contents: &str) -> Vec<TLE> {
430 let mut tles: Vec<TLE> = vec![];
431 let lines: Vec<&str> = contents.lines().collect();
432 for i_tle in 0..(lines.len() / 3) {
434 let idx: usize = 3 * i_tle;
435 let line1: &str = lines[idx];
436 let line2: &str = lines[idx + 1];
437 let line3: &str = lines[idx + 2];
438
439 let together: String = format!("{line1}\n{line2}\n{line3}\n");
440 tles.append(&mut vec![parse(&together.as_str())]);
441 }
442 return tles;
443}
444
445
446#[cfg(test)]
447mod tle_tests {
448 use super::*;
449
450 #[test]
451 fn test_calc_month_day() {
452 let year: u32 = 2023;
453 let day_of_year: u32 = 78;
454 let md = calc_month_day(day_of_year, year);
455
456 assert_eq!(md.0, 2);
457 assert_eq!(md.1, 19);
458 }
459
460 #[test]
461 fn test_check_if_leap_year() {
462 let test_year: u32 = 2022;
463 let is_leap_year: bool = check_if_leap_year(test_year);
464 assert_eq!(is_leap_year, false);
465 }
466
467 #[test]
468 fn test_parser() {
469 let sample_tle: &str = "CHANDRAYAAN-3
470 1 57320U 23098A 23208.62000000 .00000392 00000+0 00000+0 0 9994
471 2 57320 21.3360 6.1160 9054012 182.9630 18.4770 0.46841359 195";
472
473 let chandrayaan_3: TLE = parse(sample_tle);
474
475 assert_eq!(chandrayaan_3.name, "CHANDRAYAAN-3".to_string());
476
477 assert_eq!(chandrayaan_3.inc, 21.3360);
478 }
479}