tle_parser/
lib.rs

1extern crate nom;
2
3use nom::{
4    bytes::complete::{tag, take, take_until},
5    combinator::{map, map_opt, map_parser, map_res, opt, rest},
6    sequence::tuple,
7    IResult,
8};
9use serde::{Deserialize, Serialize};
10use std::{error, fmt};
11
12#[derive(Debug)]
13pub struct TLEError;
14
15pub type Result<T> = std::result::Result<T, TLEError>;
16
17impl fmt::Display for TLEError {
18    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
19        write!(f, "Invalid TLE Format")
20    }
21}
22
23impl error::Error for TLEError {
24    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
25        // Generic error, underlying cause isn't tracked.
26        None
27    }
28}
29
30#[derive(Debug, PartialEq, Serialize, Deserialize)]
31pub struct TLE {
32    pub name: String,
33    pub satellite_number: u32,
34    pub classification: char,
35    pub international_designator: String,
36    // TODO: DateTime<Utc>
37    pub epoch: String,
38    pub first_derivative_mean_motion: f64,
39    pub second_derivative_mean_motion: f64,
40    pub drag_term: f64,
41    pub ephemeris_type: u32,
42    pub element_number: u32,
43    pub inclination: f64,
44    pub right_ascension: f64,
45    pub eccentricity: f64,
46    pub argument_of_perigee: f64,
47    pub mean_anomaly: f64,
48    pub mean_motion: f64,
49    pub revolution_number: u32,
50}
51
52impl TLE {
53    pub fn to_json(&self) -> String {
54        let json = serde_json::to_string_pretty(&self).unwrap();
55        json
56    }
57}
58
59// 36258-4 => 0.36258e-4
60fn ugly_float_parser(input: &str) -> IResult<&str, f64> {
61    map_res(
62        tuple((opt(tag("-")), take_until("-"), tag("-"), rest)),
63        |(sign, a, _, b): (Option<&str>, &str, &str, &str)| {
64            format!("{}0.{}e-{}", sign.unwrap_or(""), a, b).parse::<f64>()
65        },
66    )(input)
67}
68
69fn satellite_number_parser(input: &str) -> IResult<&str, u32> {
70    map_res(take(5usize), |i: &str| i.parse::<u32>())(input)
71}
72
73fn one_space_parser(input: &str) -> IResult<&str, &str> {
74    tag(" ")(input)
75}
76
77fn take_and_trim_parser(num: usize) -> impl Fn(&str) -> IResult<&str, &str> {
78    move |x| map(take(num), |i: &str| i.trim())(x)
79}
80
81pub fn parse(raw_tle: &str) -> Result<TLE> {
82    let (
83        _,
84        (
85            name,
86            _,
87            (
88                _,
89                _,
90                satellite_number,
91                classification,
92                _,
93                international_designator,
94                _,
95                epoch,
96                _,
97                first_derivative_mean_motion,
98                _,
99                second_derivative_mean_motion,
100                _,
101                drag_term,
102                _,
103                ephemeris_type,
104                _,
105                element_number,
106                _check_sum,
107            ),
108            _,
109            // second line
110            (
111                _,
112                _,
113                _satellite_number,
114                _,
115                inclination,
116                _,
117                right_ascension,
118                _,
119                eccentricity,
120                _,
121                argument_of_perigee,
122                _,
123                mean_anomaly,
124                _,
125                mean_motion,
126                revolution_number,
127                _,
128            ),
129        ),
130    ) = tuple((
131        take_until("\n"),
132        tag("\n"),
133        // first line parser
134        map_parser(
135            take_until("\n"),
136            tuple((
137                tag("1"),
138                one_space_parser,
139                satellite_number_parser,
140                map_opt(take(1usize), |i: &str| i.chars().nth(0usize)),
141                one_space_parser,
142                take_and_trim_parser(8usize),
143                one_space_parser,
144                map(take(14usize), |i: &str| i.trim()),
145                one_space_parser,
146                map_res(take_and_trim_parser(10usize), |i: &str| i.parse::<f64>()),
147                one_space_parser,
148                map_parser(take_and_trim_parser(8usize), ugly_float_parser),
149                one_space_parser,
150                map_parser(take_and_trim_parser(8usize), ugly_float_parser),
151                one_space_parser,
152                map_res(take(1usize), |i: &str| i.parse::<u32>()),
153                one_space_parser,
154                map_res(take_and_trim_parser(4usize), |i: &str| i.parse::<u32>()),
155                map_res(take(1usize), |i: &str| i.parse::<u32>()),
156            )),
157        ),
158        tag("\n"),
159        // second line parser
160        tuple((
161            tag("2"),
162            one_space_parser,
163            satellite_number_parser,
164            one_space_parser,
165            map_res(take_and_trim_parser(8usize), |i: &str| i.parse::<f64>()),
166            one_space_parser,
167            map_res(take_and_trim_parser(8usize), |i: &str| i.parse::<f64>()),
168            one_space_parser,
169            map_res(take(7usize), |i: &str| format!("0.{}", i).parse::<f64>()),
170            one_space_parser,
171            map_res(take_and_trim_parser(8usize), |i: &str| i.parse::<f64>()),
172            one_space_parser,
173            map_res(take_and_trim_parser(8usize), |i: &str| i.parse::<f64>()),
174            one_space_parser,
175            map_res(take_and_trim_parser(11usize), |i: &str| i.parse::<f64>()),
176            map_res(take_and_trim_parser(5usize), |i: &str| i.parse::<u32>()),
177            map_res(take(1usize), |i: &str| i.parse::<u32>()),
178        )),
179    ))(raw_tle)
180    .map_err(|e| {
181        println!("🤔  Error - {}", e);
182        TLEError
183    })?;
184
185    Ok(TLE {
186        name: String::from(name),
187        satellite_number,
188        classification,
189        international_designator: String::from(international_designator),
190        epoch: String::from(epoch),
191        first_derivative_mean_motion,
192        second_derivative_mean_motion,
193        drag_term,
194        ephemeris_type,
195        element_number,
196        inclination,
197        right_ascension,
198        eccentricity,
199        argument_of_perigee,
200        mean_anomaly,
201        mean_motion,
202        revolution_number,
203    })
204}
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209
210    #[test]
211    fn parse_ugly_float() {
212        let (_, f) = ugly_float_parser("36258-4").unwrap();
213        assert_eq!(f, 0.36258e-4);
214
215        let (_, f) = ugly_float_parser("00000-0").unwrap();
216        assert_eq!(f, 0.0);
217
218        let (_, f) = ugly_float_parser("-36258-4").unwrap();
219        assert_eq!(f, -0.36258e-4);
220    }
221
222    #[test]
223    fn parse_grus_tle() {
224        let raw_tle = "GRUS-1A
2251 43890U 18111Q   20044.88470557  .00000320  00000-0  36258-4 0  9993
2262 43890  97.7009 312.6237 0003899   7.8254 352.3026 14.92889838 61757";
227
228        let tle = parse(raw_tle).unwrap();
229
230        assert_eq!(tle.name, "GRUS-1A");
231        assert_eq!(tle.satellite_number, 43890);
232        assert_eq!(tle.classification, 'U');
233        assert_eq!(tle.international_designator, "18111Q");
234        assert_eq!(tle.epoch, "20044.88470557");
235        assert_eq!(tle.first_derivative_mean_motion, 0.00000320);
236        assert_eq!(tle.second_derivative_mean_motion, 0.0);
237        assert_eq!(tle.drag_term, 0.36258e-4);
238        assert_eq!(tle.ephemeris_type, 0);
239        assert_eq!(tle.element_number, 999);
240        // 2nd line
241        assert_eq!(tle.inclination, 97.7009);
242        assert_eq!(tle.right_ascension, 312.6237);
243        assert_eq!(tle.eccentricity, 0.0003899);
244        assert_eq!(tle.argument_of_perigee, 7.8254);
245        assert_eq!(tle.mean_anomaly, 352.3026);
246        assert_eq!(tle.mean_motion, 14.92889838);
247        assert_eq!(tle.revolution_number, 6175);
248    }
249
250    #[test]
251    fn parse_iss_tle() {
252        let raw_tle = "ISS (ZARYA)
2531 25544U 98067A   20045.18587073  .00000950  00000-0  25302-4 0  9990
2542 25544  51.6443 242.0161 0004885 264.6060 207.3845 15.49165514212791";
255
256        let expected = TLE {
257            name: String::from("ISS (ZARYA)"),
258            satellite_number: 25544,
259            classification: 'U',
260            international_designator: String::from("98067A"),
261            epoch: String::from("20045.18587073"),
262            first_derivative_mean_motion: 0.00000950,
263            second_derivative_mean_motion: 0.0,
264            drag_term: 0.25302e-4,
265            ephemeris_type: 0,
266            element_number: 999,
267            inclination: 51.6443,
268            right_ascension: 242.0161,
269            eccentricity: 0.0004885,
270            argument_of_perigee: 264.6060,
271            mean_anomaly: 207.3845,
272            mean_motion: 15.49165514,
273            revolution_number: 21279,
274        };
275
276        let tle = parse(&raw_tle).unwrap();
277
278        assert_eq!(tle, expected);
279    }
280}