diff --git a/Cargo.lock b/Cargo.lock index 49555284..8faaf9e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstyle" version = "1.0.7" @@ -50,12 +65,36 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "cc" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "windows-targets 0.52.5", +] + [[package]] name = "clap" version = "4.5.4" @@ -88,6 +127,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf0a07a401f374238ab8e2f11a104d2851bf9ce711ec69804834de8af45c7af" +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + [[package]] name = "csv" version = "1.3.0" @@ -156,6 +201,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "fast-float" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95765f67b4b18863968b4a1bd5bb576f732b29a4a28c7cd84c09fa3e2875f33c" + [[package]] name = "fast_polynomial" version = "0.1.0" @@ -307,6 +358,29 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "indoc" version = "2.0.4" @@ -328,6 +402,15 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -362,6 +445,12 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + [[package]] name = "lox-bodies" version = "0.1.0" @@ -390,6 +479,15 @@ dependencies = [ "lox-utils", ] +[[package]] +name = "lox-derive" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "lox-earth" version = "0.1.0" @@ -418,10 +516,16 @@ name = "lox-io" version = "0.0.0" dependencies = [ "csv", + "fast-float", + "lox-derive", "lox-utils", "nom", + "quick-xml", + "regex", "rstest", "serde", + "serde-aux", + "serde_json", "thiserror", ] @@ -597,7 +701,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -626,9 +730,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" dependencies = [ "unicode-ident", ] @@ -722,6 +826,16 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +[[package]] +name = "quick-xml" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "quote" version = "1.0.35" @@ -910,6 +1024,17 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-aux" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d2e8bfba469d06512e11e3311d4d051a4a387a5b42d010404fecf3200321c95" +dependencies = [ + "chrono", + "serde", + "serde_json", +] + [[package]] name = "serde_derive" version = "1.0.199" @@ -921,6 +1046,17 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "slab" version = "0.4.9" @@ -938,9 +1074,9 @@ checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" [[package]] name = "syn" -version = "2.0.48" +version = "2.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" dependencies = [ "proc-macro2", "quote", @@ -1029,13 +1165,76 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.5", +] + [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -1044,13 +1243,29 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -1059,38 +1274,86 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" diff --git a/Cargo.toml b/Cargo.toml index 8c1e292c..cd100528 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,10 +19,12 @@ lox-io = { path = "crates/lox-io" } lox-space = { path = "crates/lox-space" } lox-time = { path = "crates/lox-time" } lox-utils = { path = "crates/lox-utils" } +lox-derive = { path = "./crates/lox-derive" } csv = "1.3.0" divan = "0.1.14" dyn-clone = "1.0.17" +fast-float = "0.2.0" fast_polynomial = "0.1.0" float_eq = "1.0.1" glam = "0.25.0" @@ -31,7 +33,10 @@ nom = "7.1.3" num = "0.4.1" proptest = "1.4.0" pyo3 = "0.21.1" +quick-xml = { version = "0.31.0", features = ["serde", "serialize"] } regex = "1.10.4" rstest = "0.18.2" serde = { version = "1.0.199", features = ["derive"] } +serde-aux = "4.5.0" +serde_json = "1.0.113" thiserror = "1.0" diff --git a/codecov.yaml b/codecov.yaml new file mode 100644 index 00000000..3fb7b97a --- /dev/null +++ b/codecov.yaml @@ -0,0 +1,5 @@ +ignore: + # This code is tested indirectly with high coverage but coverage tools don't + # handle proc macros + - "crates/lox-derive/src/lib.rs" + \ No newline at end of file diff --git a/crates/lox-derive/Cargo.toml b/crates/lox-derive/Cargo.toml new file mode 100644 index 00000000..32479ef2 --- /dev/null +++ b/crates/lox-derive/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "lox-derive" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0.81" +quote = "1.0.20" +syn = "2.0.63" diff --git a/crates/lox-derive/src/lib.rs b/crates/lox-derive/src/lib.rs new file mode 100644 index 00000000..8d9342a8 --- /dev/null +++ b/crates/lox-derive/src/lib.rs @@ -0,0 +1,718 @@ +/* + * Copyright (c) 2023. Helge Eichhorn and the LOX contributors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + */ + +use proc_macro2::Span; +use quote::quote; +use syn::{spanned::Spanned, DeriveInput, Field}; + +fn generate_call_to_deserializer_for_kvn_type( + type_name: &str, + type_name_new: &syn::Path, + expected_kvn_name: &str, +) -> Result { + match type_name { + "f64" | "String" | "i32" | "u64" => { + let parser = match type_name { + "String" => quote! { + crate::ndm::kvn::parser::parse_kvn_string_line_new( + next_line + ).map_err(|x| crate::ndm::kvn::KvnDeserializerErr::from(x)) + .map(|x| x.1)? + }, + "f64" => quote! { + crate::ndm::kvn::parser::parse_kvn_numeric_line_new( + next_line, + true, //@TODO + ).map_err(|x| crate::ndm::kvn::KvnDeserializerErr::from(x)) + .map(|x| x.1)? + }, + "i32" | "u64" => quote! { + crate::ndm::kvn::parser::parse_kvn_integer_line_new( + next_line, + true, //@TODO + ).map_err(|x| crate::ndm::kvn::KvnDeserializerErr::from(x)) + .map(|x| x.1)? + }, + // Assumes the match list here exhaustively matches the one from above + _ => unreachable!(), + }; + + Ok(quote! { + match lines.peek() { + None => Err(crate::ndm::kvn::KvnDeserializerErr::::UnexpectedEndOfInput { + keyword: #expected_kvn_name.to_string() + }), + Some(next_line) => { + let line_matches = crate::ndm::kvn::parser::kvn_line_matches_key_new( + #expected_kvn_name, + next_line, + )?; + + let result = if line_matches { + let next_line = lines.next().unwrap(); + + Ok(#parser.value) + } else { + Err(crate::ndm::kvn::KvnDeserializerErr::::UnexpectedKeyword { + found: next_line.to_string(), + expected: #expected_kvn_name.to_string(), + }) + }; + + result + } + } + }) + } + + "KvnNumericValue" | "KvnStringValue" | "KvnIntegerValue" => { + let parser = match type_name { + "KvnStringValue" => quote! { + crate::ndm::kvn::parser::parse_kvn_string_line( + #expected_kvn_name, + next_line, + true + ).map_err(|x| crate::ndm::kvn::KvnDeserializerErr::from(x)) + .map(|x| x.1)? + }, + "KvnNumericValue" => quote! { + crate::ndm::kvn::parser::parse_kvn_numeric_line( + #expected_kvn_name, + next_line, + true + ).map_err(|x| crate::ndm::kvn::KvnDeserializerErr::from(x)) + .map(|x| x.1)? + }, + "KvnIntegerValue" => quote! { + crate::ndm::kvn::parser::parse_kvn_integer_line( + #expected_kvn_name, + next_line, + true + ).map_err(|x| crate::ndm::kvn::KvnDeserializerErr::from(x)) + .map(|x| x.1)? + }, + // Assumes the match list here exhaustively matches the one from above + _ => unreachable!(), + }; + + Ok(quote! { + match lines.peek() { + None => Err(crate::ndm::kvn::KvnDeserializerErr::::UnexpectedEndOfInput { + keyword: #expected_kvn_name.to_string() + }), + Some(next_line) => { + let line_matches = crate::ndm::kvn::parser::kvn_line_matches_key_new( + #expected_kvn_name, + next_line, + )?; + + let result = if line_matches { + let next_line = lines.next().unwrap(); + + Ok(#parser) + } else { + Err(crate::ndm::kvn::KvnDeserializerErr::::UnexpectedKeyword { + found: next_line.to_string(), + expected: #expected_kvn_name.to_string(), + }) + }; + + result + } + } + }) + } + + _ => Ok(quote! { + { + let has_next_line = lines.peek().is_some(); + + let result = if has_next_line { + #type_name_new::deserialize(lines) + } else { + Err(crate::ndm::kvn::KvnDeserializerErr::::UnexpectedEndOfInput { + keyword: #expected_kvn_name.to_string() + }) + }; + + result + } + }), + } +} + +//@TODO unify with above +fn generate_call_to_deserializer_for_kvn_type_new( + type_name: &str, + type_name_new: &syn::Path, +) -> Result { + match type_name { + "String" | "f64" | "NonNegativeDouble" | "NegativeDouble" | "PositiveDouble" | "i32" => { + let parser = match type_name { + "String" => { + quote! { + crate::ndm::kvn::parser::parse_kvn_string_line_new( + lines.next().unwrap() + ).map_err(|x| crate::ndm::kvn::KvnDeserializerErr::from(x)) + .map(|x| x.1)? + } + } + "f64" | "NonNegativeDouble" | "NegativeDouble" | "PositiveDouble" => quote! { + crate::ndm::kvn::parser::parse_kvn_numeric_line_new( + lines.next().unwrap(), + true + ).map_err(|x| crate::ndm::kvn::KvnDeserializerErr::from(x)) + .map(|x| x.1)? + }, + "i32" => quote! { + crate::ndm::kvn::parser::parse_kvn_integer_line_new( + lines.next().unwrap(), + true + ).map_err(|x| crate::ndm::kvn::KvnDeserializerErr::from(x)) + .map(|x| x.1)? + }, + // Assumes the match list here exhaustively matches the one from above + _ => unreachable!(), + }; + + Ok(parser) + } + _ => { + Ok(quote! { + { + let has_next_line = lines.peek().is_some(); + + let result = if has_next_line { + #type_name_new::deserialize(lines) + } else { + Err(crate::ndm::kvn::KvnDeserializerErr::::UnexpectedEndOfInput { + keyword: "Blala"to_string() //@TODO + }) + }; + + result + } + }) + } + } +} + +fn get_generic_type_argument(field: &Field) -> Option { + if let syn::Type::Path(type_path) = &field.ty { + let path_part = type_path.path.segments.first(); + if let Some(path_part) = path_part { + if let syn::PathArguments::AngleBracketed(type_argument) = &path_part.arguments { + return Some( + type_argument + .args + .first() + .unwrap() + .span() + .source_text() + .unwrap(), + ); + } + } + } + + None +} + +fn get_generic_type_argument_new(field: &Field) -> Option<&syn::Path> { + if let syn::Type::Path(type_path) = &field.ty { + let path_part = type_path.path.segments.first(); + if let Some(path_part) = path_part { + if let syn::PathArguments::AngleBracketed(type_argument) = &path_part.arguments { + if let Some(syn::GenericArgument::Type(r#type)) = &type_argument.args.first() { + return extract_type_path(r#type); + } + } + } + } + + None +} + +fn generate_call_to_deserializer_for_option_type( + expected_kvn_name: &str, + field: &Field, +) -> Result { + let type_name = get_generic_type_argument(field); + + // @TODO + let type_name_new = get_generic_type_argument_new(field).unwrap(); + + match type_name { + None => Err(syn::Error::new_spanned( + field, + "Malformed type for `#[derive(KvnDeserialize)]`", + ) + .into_compile_error() + .into()), + + Some(type_name) => { + let deserializer_for_kvn_type = generate_call_to_deserializer_for_kvn_type( + type_name.as_ref(), + type_name_new, + expected_kvn_name, + )?; + + let condition_shortcut = match type_name.as_str() { + "String" | "f64" | "i32" | "u64" => quote! {}, + _ => quote! { ! #type_name_new::should_check_key_match() || }, + }; + + Ok(quote! { + match lines.peek() { + None => None, + Some(next_line) => { + let line_matches = crate::ndm::kvn::parser::kvn_line_matches_key_new( + #expected_kvn_name, + next_line, + )?; + + if #condition_shortcut line_matches { + let result = #deserializer_for_kvn_type; + + match result { + Ok(item) => Some(item), + Err(crate::ndm::kvn::KvnDeserializerErr::UnexpectedKeyword { .. }) | + Err(crate::ndm::kvn::KvnDeserializerErr::UnexpectedEndOfInput { .. }) => None, + Err(e) => Err(e)?, + } + } else { + None + } + } + } + }) + } + } +} + +fn generate_call_to_deserializer_for_vec_type( + expected_kvn_name: &str, + field: &Field, +) -> Result { + if let syn::Type::Path(type_path) = &field.ty { + let path_part = type_path.path.segments.first(); + if let Some(path_part) = path_part { + if let syn::PathArguments::AngleBracketed(type_argument) = &path_part.arguments { + let type_name = type_argument + .args + .first() + .unwrap() + .span() + .source_text() + .unwrap(); + + //@TODO + let bla = get_generic_type_argument_new(field).unwrap(); + + let expected_kvn_name = expected_kvn_name.trim_end_matches("_LIST"); + + let deserializer_for_kvn_type = generate_call_to_deserializer_for_kvn_type( + type_name.as_ref(), + bla, + expected_kvn_name, + )?; + + let type_ident = syn::Ident::new(&type_name, Span::call_site()); + + return Ok(quote! { + { + let mut items: Vec<#type_ident> = Vec::new(); + + loop { + let result = #deserializer_for_kvn_type; + + match result { + Ok(item) => items.push(item), + Err(crate::ndm::kvn::KvnDeserializerErr::UnexpectedKeyword { .. }) | + Err(crate::ndm::kvn::KvnDeserializerErr::UnexpectedEndOfInput { .. }) => break, + Err(e) => Err(e)?, + } + } + + items + } + }); + } + } + } + + Err( + syn::Error::new_spanned(field, "Malformed type for `#[derive(KvnDeserialize)]`") + .into_compile_error() + .into(), + ) +} + +fn is_value_unit_struct(item: &DeriveInput) -> bool { + item.attrs.iter().any(|attr| { + attr.path().is_ident("kvn") + && attr + .parse_nested_meta(|meta| { + if meta.path.is_ident("value_unit_struct") { + Ok(()) + } else { + Err(meta.error("unsupported attribute")) + } + }) + .is_ok() + }) +} + +fn extract_type_path(ty: &syn::Type) -> Option<&syn::Path> { + match *ty { + syn::Type::Path(ref typepath) if typepath.qself.is_none() => Some(&typepath.path), + _ => None, + } +} + +fn handle_version_field( + type_name: &proc_macro2::Ident, + field: &syn::Field, +) -> Result { + let string_type_name = type_name.to_string(); + + if !string_type_name.ends_with("Type") { + return Err(syn::Error::new_spanned( + type_name, + "Types with \"version\" field should be of the form SomethingType", + ) + .into_compile_error()); + } + + let message_type_name = string_type_name + .trim_end_matches("Type") + .to_string() + .to_uppercase(); + + let field_name = field.ident.as_ref().unwrap(); + + // 7.4.4 Keywords must be uppercase and must not contain blanks + let expected_kvn_name = format!("CCSDS_{}_VERS", message_type_name); + + // Unwrap is okay because we expect this span to come from the source code + let field_type = extract_type_path(&field.ty) + .unwrap() + .span() + .source_text() + .unwrap(); + let field_type_new = extract_type_path(&field.ty).unwrap(); + + let parser = generate_call_to_deserializer_for_kvn_type( + &field_type, + field_type_new, + &expected_kvn_name, + )?; + + Ok(quote! { + #field_name: #parser?, + }) +} + +fn deserializer_for_struct_with_named_fields( + type_name: &proc_macro2::Ident, + fields: &syn::punctuated::Punctuated, + is_value_unit_struct: bool, +) -> proc_macro2::TokenStream { + if &type_name.to_string() == "UserDefinedType" { + //@TODO + return quote! { + Ok(Default::default()) + }; + } + + if is_value_unit_struct { + let mut deserializer = None; + let mut unit_type: Option = None; + let mut unit_field_name_ident: Option<&proc_macro2::Ident> = None; + let mut field_type: Option = None; + let mut field_type_new: Option<&syn::Path> = None; + + for (index, field) in fields.iter().enumerate() { + let field_name_ident = field.ident.as_ref().unwrap(); + + // Unwrap is okay because we only support named structs + let field_name = field_name_ident.span().source_text().unwrap(); + + match index { + 0 => { + if field_name.as_str() != "base" { + return syn::Error::new_spanned( + field, + "The first field in a value unit struct should be called \"base\"", + ) + .into_compile_error(); + } + + // Unwrap is okay because we expect this span to come from the source code + let local_field_type = extract_type_path(&field.ty) + .unwrap() + .span() + .source_text() + .unwrap(); + let local_field_type_new = extract_type_path(&field.ty).unwrap(); + + match local_field_type.as_str() { + "KvnDateTimeValue" | "String" | "f64" | "i32" | "NonNegativeDouble" + | "NegativeDouble" | "PositiveDouble" => { + match generate_call_to_deserializer_for_kvn_type_new( + &local_field_type, + local_field_type_new, + ) { + Ok(deserializer_for_kvn_type) => { + deserializer = Some(deserializer_for_kvn_type) + } + Err(e) => return e.into(), + } + } + + _ => { + return syn::Error::new_spanned( + field, + "Unsupported field type for deserializer", + ) + .into_compile_error() + } + }; + + field_type = Some(local_field_type); + field_type_new = Some(local_field_type_new); + } + 1 => { + if field_name.as_str() != "units" && field_name.as_str() != "parameter" { + return syn::Error::new_spanned( + field, + "The second field in a value unit struct should be called \"units\" or \"parameter\"", + ) + .into_compile_error(); + } + + unit_type = get_generic_type_argument(field); + unit_field_name_ident = Some(field_name_ident); + } + _ => { + return syn::Error::new_spanned( + field, + "Only two fields are allowed: \"base\" and (\"units\" or \"parameters\"", + ) + .into_compile_error() + } + } + } + + // This unwrap is okay because we know the field exists. If it didn't exist we would've thrown an error. + let unit_type = unit_type.unwrap(); + let unit_field_name_ident = unit_field_name_ident.unwrap(); + let field_type = field_type.unwrap(); + let field_type_new = field_type_new.unwrap(); + + let unit_type_ident = syn::Ident::new(&unit_type, Span::call_site()); + + let base = match field_type.as_str() { + "NonNegativeDouble" | "NegativeDouble" | "PositiveDouble" => { + quote! { #field_type_new (kvn_value.value) } + } + _ => quote! { kvn_value.value }, + }; + + match deserializer { + None => syn::Error::new_spanned(fields, "Unable to create deserializer for struct") + .into_compile_error(), + Some(deserializer) => quote! { + let kvn_value = #deserializer; + Ok(#type_name { + base: #base, + #unit_field_name_ident: kvn_value.unit.map(|unit| #unit_type_ident (unit)), + }) + }, + } + } else { + let other_field_deserializers: Result, _> = fields + .iter() + .map(|field| { + let field_name = field.ident.as_ref().unwrap(); + + // Unwrap is okay because we only support named structs + // 7.4.4 Keywords must be uppercase and must not contain blanks + let expected_kvn_name = field_name.span().source_text().unwrap().to_uppercase(); + + // Unwrap is okay because we expect this span to come from the source code + let field_type = extract_type_path(&field.ty).unwrap().span().source_text().unwrap(); + let field_type_new = extract_type_path(&field.ty).unwrap(); + + if field_name == "version" { + return handle_version_field(type_name, field); + } + + let parser = match field_type.as_str() { + "KvnStringValue" | "KvnNumericValue" | "KvnIntegerValue" | "String" | "f64" | "i32" => { + let deserializer_for_kvn_type = generate_call_to_deserializer_for_kvn_type( + &field_type, + field_type_new, + &expected_kvn_name, + )?; + + quote! { + #deserializer_for_kvn_type? + } + } + "Option" => { + generate_call_to_deserializer_for_option_type(&expected_kvn_name, field)? + } + "Vec" => generate_call_to_deserializer_for_vec_type(&expected_kvn_name, field)?, + _ => { + + let condition_shortcut = match field_type.as_str() { + "String" => quote! {}, + _ => quote! { ! #field_type_new::should_check_key_match() || }, + }; + + quote! { + match lines.peek() { + None => Err(crate::ndm::kvn::KvnDeserializerErr::::UnexpectedEndOfInput { + keyword: #expected_kvn_name.to_string() + })?, + Some(next_line) => { + let line_matches = crate::ndm::kvn::parser::kvn_line_matches_key_new( + #expected_kvn_name, + next_line, + )?; + + if #condition_shortcut line_matches { + #field_type_new::deserialize(lines)? + } else { + Err(crate::ndm::kvn::KvnDeserializerErr::::UnexpectedKeyword { + found: next_line.to_string(), + expected: #expected_kvn_name.to_string(), + })? + } + } + } + } + } + }; + + Ok(quote! { + #field_name: #parser, + }) + }) + .collect(); + + if let Err(e) = other_field_deserializers { + return e; + } + + let other_field_deserializers = other_field_deserializers.unwrap(); + + quote! { + Ok(#type_name { + #(#other_field_deserializers)* + }) + } + } +} + +fn deserializers_for_struct_with_unnamed_fields( + type_name: &proc_macro2::Ident, + fields: &syn::punctuated::Punctuated, +) -> proc_macro2::TokenStream { + let field = fields + .first() + .expect("We expect exactly one item in structs with unnamed fields"); + + if &type_name.to_string() == "EpochType" { + return quote! { + Ok(#type_name ( + crate::ndm::kvn::parser::parse_kvn_datetime_line_new( + lines.next().unwrap() + ).map_err(|x| crate::ndm::kvn::KvnDeserializerErr::from(x)) + .map(|x| x.1)?.full_value + )) + }; + } + + // Unwrap is okay because we expect this span to come from the source code + let field_type = extract_type_path(&field.ty) + .unwrap() + .span() + .source_text() + .unwrap(); + let field_type_new = extract_type_path(&field.ty).unwrap(); + + let deserializer_for_kvn_type = + generate_call_to_deserializer_for_kvn_type_new(&field_type, field_type_new); + + let deserializer_for_kvn_type = match deserializer_for_kvn_type { + Ok(deserializer_for_kvn_type) => deserializer_for_kvn_type, + Err(e) => return e.into(), + }; + + quote! { + Ok(#type_name ( + #deserializer_for_kvn_type.value + )) + } +} + +#[proc_macro_derive(KvnDeserialize, attributes(kvn))] +pub fn derive_kvn_deserialize(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let item = syn::parse_macro_input!(item as syn::DeriveInput); + let type_name = &item.ident; + let is_value_unit_struct = is_value_unit_struct(&item); + + let syn::Data::Struct(strukt) = item.data else { + return syn::Error::new_spanned( + &item, + "only structs are supported for `#[derive(KvnDeserialize)]`", + ) + .into_compile_error() + .into(); + }; + + let (struct_deserializer, should_check_key_match) = match strukt.fields { + syn::Fields::Named(syn::FieldsNamed { named, .. }) => ( + deserializer_for_struct_with_named_fields(type_name, &named, is_value_unit_struct), + is_value_unit_struct, + ), + syn::Fields::Unnamed(syn::FieldsUnnamed { unnamed, .. }) => ( + deserializers_for_struct_with_unnamed_fields(type_name, &unnamed), + true, + ), + _ => { + return syn::Error::new_spanned( + &strukt.fields, + "only named fields are supported for `#[derive(KvnDeserialize)]`", + ) + .into_compile_error() + .into() + } + }; + + let (impl_generics, type_generics, where_clause) = item.generics.split_for_impl(); + + let struct_deserializer = quote! { + impl #impl_generics crate::ndm::kvn::KvnDeserializer for #type_name #type_generics + #where_clause + { + fn deserialize<'a>(lines: &mut ::std::iter::Peekable>) + -> Result<#type_name, crate::ndm::kvn::KvnDeserializerErr> { + #struct_deserializer + } + + fn should_check_key_match () -> bool { + #should_check_key_match + } + } + }; + + struct_deserializer.into() +} diff --git a/crates/lox-io/Cargo.toml b/crates/lox-io/Cargo.toml index ec073660..72646987 100644 --- a/crates/lox-io/Cargo.toml +++ b/crates/lox-io/Cargo.toml @@ -15,5 +15,12 @@ nom.workspace = true serde.workspace = true thiserror.workspace = true +quick-xml.workspace = true +serde_json.workspace = true +serde-aux.workspace = true +regex.workspace = true +fast-float.workspace = true +lox-derive.workspace = true + [dev-dependencies] rstest.workspace = true diff --git a/crates/lox-io/src/lib.rs b/crates/lox-io/src/lib.rs index 78728fbc..87ec8707 100644 --- a/crates/lox-io/src/lib.rs +++ b/crates/lox-io/src/lib.rs @@ -7,4 +7,5 @@ */ pub mod iers; +pub mod ndm; pub mod spice; diff --git a/crates/lox-io/src/ndm.rs b/crates/lox-io/src/ndm.rs new file mode 100644 index 00000000..0c0431f3 --- /dev/null +++ b/crates/lox-io/src/ndm.rs @@ -0,0 +1,45 @@ +//! Parsers for CCSDS +//! [Navigation Data Messages version 3](https://public.ccsds.org/Pubs/502x0b3e1.pdf). +//! +//! The data types used to deserialize XML and KVN are generated from the CCSDS +//! NDM schema description. Some slight alterations have been made on top to +//! improve user friendliness and compatibility. +//! +//! Since Rust XML and JSON deserializers are slightly incompatible, the JSON +//! deserializer is separate. +//! +//! Because there are a signficant number of messages out there that do not +//! strictly comply with the specification, the parsers are relaxed in terms of +//! input that they accept. Some relaxations: +//! +//! - The KVN floating point numbers defined in the specification can have only +//! one character in the integer part of the number, but we accept any regular +//! float number. +//! - The KVN strings are defined in the specification as being either only +//! lower-case or only upper-case, but we accept any combination of cases. +//! +//! The XML deserializer does not perform any validation on the schema types +//! defined (e.g. non-positive double, lat-long, angle). The validation only +//! checks if the data can be parsed into the fundamental Rust data types +//! (e.g. f64, u64). +//! +//! The KVN parsing is implemented with a finite-state parser. As such, it is +//! eager and has no backtracking. This is normally okay. But the KVN grammar +//! is ambiguous with regards to its `COMMENT` fields, so in some corner-cases +//! it can lead to some `COMMENT` lines being discarded. This happens when an +//! optional section is ommitted. For example, an OPM message can have an empty +//! covariance matrix section. But this will cause the comments for the +//! following section, the maneuver parameters list, to be discarded. +//! +//! Check the respective submodules for more information. + +pub mod json; +pub mod kvn; +pub mod xml; + +pub mod common; +pub mod ndm_ci; +pub mod ocm; +pub mod oem; +pub mod omm; +pub mod opm; diff --git a/crates/lox-io/src/ndm/common.rs b/crates/lox-io/src/ndm/common.rs new file mode 100644 index 00000000..9d05fcd5 --- /dev/null +++ b/crates/lox-io/src/ndm/common.rs @@ -0,0 +1,1061 @@ +/* + * Copyright (c) 2023. Helge Eichhorn and the LOX contributors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + */ + +//! Data types shared between different NDM message types + +use serde; + +#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct AccUnits(#[serde(rename = "$text")] pub String); + +#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct AngleUnits(#[serde(rename = "$text")] pub String); + +#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct AngleRateUnits(#[serde(rename = "$text")] pub String); + +#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct AreaUnits(#[serde(rename = "$text")] pub String); + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct DayIntervalUnits(#[serde(rename = "$text")] pub String); + +#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct GmUnits(#[serde(rename = "$text")] pub String); + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct LengthUnits(#[serde(rename = "$text")] pub String); + +#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct MassUnits(#[serde(rename = "$text")] pub String); + +#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct MomentUnits(#[serde(rename = "$text")] pub String); + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct WkgUnits(#[serde(rename = "$text")] pub String); + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct ObjectDescriptionType(#[serde(rename = "$text")] pub String); + +#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct PositionUnits(#[serde(rename = "$text")] pub String); + +#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct VelocityUnits(#[serde(rename = "$text")] pub String); + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct Vec3Double(#[serde(rename = "$text")] pub f64); + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct EpochType(#[serde(rename = "$text")] pub String); + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct TimeUnits(#[serde(rename = "$text")] pub String); + +#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct NegativeDouble(#[serde(rename = "$text")] pub f64); + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct NonNegativeDouble(#[serde(rename = "$text")] pub f64); + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct PositiveDouble(#[serde(rename = "$text")] pub f64); + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct Range100Type(#[serde(rename = "$text")] pub String); + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct ProbabilityType(#[serde(rename = "$text")] pub String); + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct PercentageUnits(#[serde(rename = "$text")] pub String); + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct TrajBasisType(#[serde(rename = "$text")] pub String); + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct RevNumBasisType(#[serde(rename = "$text")] pub String); + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct CovBasisType(#[serde(rename = "$text")] pub String); + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct ManBasisType(#[serde(rename = "$text")] pub String); + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct ManDcType(#[serde(rename = "$text")] pub String); + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct NumPerYearUnits(#[serde(rename = "$text")] pub String); + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct ThrustUnits(#[serde(rename = "$text")] pub String); + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct CovOrderType(#[serde(rename = "$text")] pub String); + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct GeomagUnits(#[serde(rename = "$text")] pub String); + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct SolarFluxUnits(#[serde(rename = "$text")] pub String); + +#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct PositionCovarianceUnits(#[serde(rename = "$text")] pub String); + +#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct VelocityCovarianceUnits(#[serde(rename = "$text")] pub String); + +#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct PositionVelocityCovarianceUnits(#[serde(rename = "$text")] pub String); + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct OdmHeader { + #[serde(rename = "COMMENT")] + pub comment_list: Vec, + #[serde(rename = "CLASSIFICATION")] + pub classification_list: Vec, + #[serde(rename = "CREATION_DATE")] + pub creation_date: EpochType, + #[serde(rename = "ORIGINATOR")] + pub originator: String, + #[serde(rename = "MESSAGE_ID")] + pub message_id: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +#[kvn(value_unit_struct)] +pub struct AccType { + #[serde(rename = "$text")] + pub base: f64, + #[serde(rename = "@units")] + pub units: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +#[kvn(value_unit_struct)] +pub struct AngleType { + #[serde(rename = "$text")] + pub base: f64, + #[serde(rename = "@units")] + pub units: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +#[kvn(value_unit_struct)] +pub struct AngleRateType { + #[serde(rename = "$text")] + pub base: f64, + #[serde(rename = "@units")] + pub units: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +#[kvn(value_unit_struct)] +pub struct AreaType { + #[serde(rename = "$text")] + pub base: NonNegativeDouble, + #[serde(rename = "@units")] + pub units: Option, +} + +#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct DayIntervalType { + #[serde(rename = "$text")] + pub base: NonNegativeDouble, + #[serde(rename = "@units")] + pub units: DayIntervalUnits, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct OcmDayIntervalType { + #[serde(rename = "$text")] + pub base: NonNegativeDouble, + #[serde(rename = "@units")] + pub units: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +#[kvn(value_unit_struct)] +pub struct DeltamassType { + #[serde(rename = "$text")] + pub base: NegativeDouble, + #[serde(rename = "@units")] + pub units: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +#[kvn(value_unit_struct)] +pub struct GmType { + #[serde(rename = "$text")] + pub base: PositiveDouble, + #[serde(rename = "@units")] + pub units: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +#[kvn(value_unit_struct)] +pub struct InclinationType { + #[serde(rename = "$text")] + pub base: f64, + #[serde(rename = "@units")] + pub units: Option, +} + +#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct LengthType { + #[serde(rename = "$text")] + pub base: f64, + #[serde(rename = "@units")] + pub units: LengthUnits, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct OcmLengthType { + #[serde(rename = "$text")] + pub base: f64, + #[serde(rename = "@units")] + pub units: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +#[kvn(value_unit_struct)] +pub struct MassType { + #[serde(rename = "$text")] + pub base: NonNegativeDouble, + #[serde(rename = "@units")] + pub units: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +#[kvn(value_unit_struct)] +pub struct MomentType { + #[serde(rename = "$text")] + pub base: f64, + #[serde(rename = "@units")] + pub units: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct WkgType { + #[serde(rename = "$text")] + pub base: NonNegativeDouble, + #[serde(rename = "@units")] + pub units: WkgUnits, +} + +#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct OdParametersType { + #[serde(rename = "COMMENT")] + pub comment_list: Vec, + #[serde(rename = "TIME_LASTOB_START")] + pub time_lastob_start: Option, + #[serde(rename = "TIME_LASTOB_END")] + pub time_lastob_end: Option, + #[serde(rename = "RECOMMENDED_OD_SPAN")] + pub recommended_od_span: Option, + #[serde(rename = "ACTUAL_OD_SPAN")] + pub actual_od_span: Option, + #[serde(rename = "OBS_AVAILABLE")] + pub obs_available: Option, + #[serde(rename = "OBS_USED")] + pub obs_used: Option, + #[serde(rename = "TRACKS_AVAILABLE")] + pub tracks_available: Option, + #[serde(rename = "TRACKS_USED")] + pub tracks_used: Option, + #[serde(rename = "RESIDUALS_ACCEPTED")] + pub residuals_accepted: Option, + #[serde(rename = "WEIGHTED_RMS")] + pub weighted_rms: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct SpacecraftParametersType { + #[serde(rename = "COMMENT")] + pub comment_list: Vec, + #[serde(rename = "MASS")] + pub mass: Option, + #[serde(rename = "SOLAR_RAD_AREA")] + pub solar_rad_area: Option, + #[serde(rename = "SOLAR_RAD_COEFF")] + pub solar_rad_coeff: Option, + #[serde(rename = "DRAG_AREA")] + pub drag_area: Option, + #[serde(rename = "DRAG_COEFF")] + pub drag_coeff: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct StateVectorType { + #[serde(rename = "COMMENT")] + pub comment_list: Vec, + #[serde(rename = "EPOCH")] + pub epoch: EpochType, + #[serde(rename = "X")] + pub x: PositionType, + #[serde(rename = "Y")] + pub y: PositionType, + #[serde(rename = "Z")] + pub z: PositionType, + #[serde(rename = "X_DOT")] + pub x_dot: VelocityType, + #[serde(rename = "Y_DOT")] + pub y_dot: VelocityType, + #[serde(rename = "Z_DOT")] + pub z_dot: VelocityType, +} + +#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct StateVectorAccType { + #[serde(rename = "EPOCH")] + pub epoch: EpochType, + #[serde(rename = "X")] + pub x: PositionType, + #[serde(rename = "Y")] + pub y: PositionType, + #[serde(rename = "Z")] + pub z: PositionType, + #[serde(rename = "X_DOT")] + pub x_dot: VelocityType, + #[serde(rename = "Y_DOT")] + pub y_dot: VelocityType, + #[serde(rename = "Z_DOT")] + pub z_dot: VelocityType, + #[serde(rename = "X_DDOT")] + pub x_ddot: Option, + #[serde(rename = "Y_DDOT")] + pub y_ddot: Option, + #[serde(rename = "Z_DDOT")] + pub z_ddot: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +#[kvn(value_unit_struct)] +pub struct DistanceType { + #[serde(rename = "$text")] + pub base: f64, + #[serde(rename = "@units")] + pub units: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +#[kvn(value_unit_struct)] +pub struct PositionType { + #[serde(rename = "$text")] + pub base: f64, + #[serde(rename = "@units")] + pub units: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +#[kvn(value_unit_struct)] +pub struct VelocityType { + #[serde(rename = "$text")] + pub base: f64, + #[serde(rename = "@units")] + pub units: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +#[kvn(value_unit_struct)] +pub struct DurationType { + #[serde(rename = "$text")] + pub base: NonNegativeDouble, + #[serde(rename = "@units")] + pub units: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct TimeOffsetType { + #[serde(rename = "$text")] + pub base: f64, + #[serde(rename = "@units")] + pub units: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct PercentageType { + #[serde(rename = "$text")] + pub base: Range100Type, + #[serde(rename = "@units")] + pub units: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct ManeuverFreqType { + #[serde(rename = "$text")] + pub base: NonNegativeDouble, + #[serde(rename = "@units")] + pub units: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct ThrustType { + #[serde(rename = "$text")] + pub base: f64, + #[serde(rename = "@units")] + pub units: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct GeomagType { + #[serde(rename = "$text")] + pub base: f64, + #[serde(rename = "@units")] + pub units: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct SolarFluxType { + #[serde(rename = "$text")] + pub base: f64, + #[serde(rename = "@units")] + pub units: Option, +} + +#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct OemCovarianceMatrixType { + #[serde(rename = "COMMENT")] + pub comment_list: Vec, + #[serde(rename = "EPOCH")] + pub epoch: EpochType, + #[serde(rename = "COV_REF_FRAME")] + pub cov_ref_frame: Option, + #[serde(rename = "CX_X")] + pub cx_x: PositionCovarianceType, + #[serde(rename = "CY_X")] + pub cy_x: PositionCovarianceType, + #[serde(rename = "CY_Y")] + pub cy_y: PositionCovarianceType, + #[serde(rename = "CZ_X")] + pub cz_x: PositionCovarianceType, + #[serde(rename = "CZ_Y")] + pub cz_y: PositionCovarianceType, + #[serde(rename = "CZ_Z")] + pub cz_z: PositionCovarianceType, + #[serde(rename = "CX_DOT_X")] + pub cx_dot_x: PositionVelocityCovarianceType, + #[serde(rename = "CX_DOT_Y")] + pub cx_dot_y: PositionVelocityCovarianceType, + #[serde(rename = "CX_DOT_Z")] + pub cx_dot_z: PositionVelocityCovarianceType, + #[serde(rename = "CX_DOT_X_DOT")] + pub cx_dot_x_dot: VelocityCovarianceType, + #[serde(rename = "CY_DOT_X")] + pub cy_dot_x: PositionVelocityCovarianceType, + #[serde(rename = "CY_DOT_Y")] + pub cy_dot_y: PositionVelocityCovarianceType, + #[serde(rename = "CY_DOT_Z")] + pub cy_dot_z: PositionVelocityCovarianceType, + #[serde(rename = "CY_DOT_X_DOT")] + pub cy_dot_x_dot: VelocityCovarianceType, + #[serde(rename = "CY_DOT_Y_DOT")] + pub cy_dot_y_dot: VelocityCovarianceType, + #[serde(rename = "CZ_DOT_X")] + pub cz_dot_x: PositionVelocityCovarianceType, + #[serde(rename = "CZ_DOT_Y")] + pub cz_dot_y: PositionVelocityCovarianceType, + #[serde(rename = "CZ_DOT_Z")] + pub cz_dot_z: PositionVelocityCovarianceType, + #[serde(rename = "CZ_DOT_X_DOT")] + pub cz_dot_x_dot: VelocityCovarianceType, + #[serde(rename = "CZ_DOT_Y_DOT")] + pub cz_dot_y_dot: VelocityCovarianceType, + #[serde(rename = "CZ_DOT_Z_DOT")] + pub cz_dot_z_dot: VelocityCovarianceType, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct OpmCovarianceMatrixType { + #[serde(rename = "COMMENT")] + pub comment_list: Vec, + #[serde(rename = "COV_REF_FRAME")] + pub cov_ref_frame: Option, + #[serde(rename = "CX_X")] + pub cx_x: PositionCovarianceType, + #[serde(rename = "CY_X")] + pub cy_x: PositionCovarianceType, + #[serde(rename = "CY_Y")] + pub cy_y: PositionCovarianceType, + #[serde(rename = "CZ_X")] + pub cz_x: PositionCovarianceType, + #[serde(rename = "CZ_Y")] + pub cz_y: PositionCovarianceType, + #[serde(rename = "CZ_Z")] + pub cz_z: PositionCovarianceType, + #[serde(rename = "CX_DOT_X")] + pub cx_dot_x: PositionVelocityCovarianceType, + #[serde(rename = "CX_DOT_Y")] + pub cx_dot_y: PositionVelocityCovarianceType, + #[serde(rename = "CX_DOT_Z")] + pub cx_dot_z: PositionVelocityCovarianceType, + #[serde(rename = "CX_DOT_X_DOT")] + pub cx_dot_x_dot: VelocityCovarianceType, + #[serde(rename = "CY_DOT_X")] + pub cy_dot_x: PositionVelocityCovarianceType, + #[serde(rename = "CY_DOT_Y")] + pub cy_dot_y: PositionVelocityCovarianceType, + #[serde(rename = "CY_DOT_Z")] + pub cy_dot_z: PositionVelocityCovarianceType, + #[serde(rename = "CY_DOT_X_DOT")] + pub cy_dot_x_dot: VelocityCovarianceType, + #[serde(rename = "CY_DOT_Y_DOT")] + pub cy_dot_y_dot: VelocityCovarianceType, + #[serde(rename = "CZ_DOT_X")] + pub cz_dot_x: PositionVelocityCovarianceType, + #[serde(rename = "CZ_DOT_Y")] + pub cz_dot_y: PositionVelocityCovarianceType, + #[serde(rename = "CZ_DOT_Z")] + pub cz_dot_z: PositionVelocityCovarianceType, + #[serde(rename = "CZ_DOT_X_DOT")] + pub cz_dot_x_dot: VelocityCovarianceType, + #[serde(rename = "CZ_DOT_Y_DOT")] + pub cz_dot_y_dot: VelocityCovarianceType, + #[serde(rename = "CZ_DOT_Z_DOT")] + pub cz_dot_z_dot: VelocityCovarianceType, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +#[kvn(value_unit_struct)] +pub struct PositionCovarianceType { + #[serde(rename = "$text")] + pub base: f64, + #[serde(rename = "@units")] + pub units: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +#[kvn(value_unit_struct)] +pub struct VelocityCovarianceType { + #[serde(rename = "$text")] + pub base: f64, + #[serde(rename = "@units")] + pub units: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +#[kvn(value_unit_struct)] +pub struct PositionVelocityCovarianceType { + #[serde(rename = "$text")] + pub base: f64, + #[serde(rename = "@units")] + pub units: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct UserDefinedType { + #[serde(rename = "COMMENT")] + pub comment_list: Vec, + #[serde(rename = "USER_DEFINED")] + pub user_defined_list: Vec, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct UserDefinedParameterType { + #[serde(rename = "$text")] + pub base: String, + #[serde(rename = "@parameter")] + pub parameter: String, +} diff --git a/crates/lox-io/src/ndm/json.rs b/crates/lox-io/src/ndm/json.rs new file mode 100644 index 00000000..9495e33b --- /dev/null +++ b/crates/lox-io/src/ndm/json.rs @@ -0,0 +1,11 @@ +//! Deserializers for some JSON message types +//! +//! JSON message types are not fully standardized at CCSDS level. But they are +//! sometimes used in the wild due to the abundant availability of parsers. +//! +//! Only OMM messages were found to be used in the wild, so it is the only one +//! implemented. +//! +//! The data type is manually derived from the XML schema. + +pub mod omm; diff --git a/crates/lox-io/src/ndm/json/omm.rs b/crates/lox-io/src/ndm/json/omm.rs new file mode 100644 index 00000000..f345acfe --- /dev/null +++ b/crates/lox-io/src/ndm/json/omm.rs @@ -0,0 +1,556 @@ +/* + * Copyright (c) 2023. Helge Eichhorn and the LOX contributors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + */ + +//! Deserializers for JSON CCSDS Orbit Mean Elements Message +//! +//! To deserialize a JSON message: +//! +//! ``` +//! # use lox_io::ndm::json::omm::OmmType; +//! +//! # let json = r#"{ +//! # "CCSDS_OMM_VERS": "2.0", +//! # "COMMENT": "GENERATED VIA SPACE-TRACK.ORG API", +//! # "CREATION_DATE": "2020-12-29T06:26:10", +//! # "ORIGINATOR": "18 SPCS", +//! # "OBJECT_NAME": "NUSAT-8 (MARIE)", +//! # "OBJECT_ID": "2020-003C", +//! # "CENTER_NAME": "EARTH", +//! # "REF_FRAME": "TEME", +//! # "TIME_SYSTEM": "UTC", +//! # "MEAN_ELEMENT_THEORY": "SGP4", +//! # "EPOCH": "2020-12-29T03:57:59.406624", +//! # "MEAN_MOTION": "15.27989249", +//! # "ECCENTRICITY": "0.00133560", +//! # "INCLINATION": "97.2970", +//! # "RA_OF_ASC_NODE": "66.4161", +//! # "ARG_OF_PERICENTER": "110.6345", +//! # "MEAN_ANOMALY": "334.7107", +//! # "EPHEMERIS_TYPE": "0", +//! # "CLASSIFICATION_TYPE": "U", +//! # "NORAD_CAT_ID": "45018", +//! # "ELEMENT_SET_NO": "999", +//! # "REV_AT_EPOCH": "5327", +//! # "BSTAR": "0.00008455300000", +//! # "MEAN_MOTION_DOT": "0.00002241", +//! # "MEAN_MOTION_DDOT": "0.0000000000000", +//! # "SEMIMAJOR_AXIS": "6859.961", +//! # "PERIOD": "94.242", +//! # "APOAPSIS": "490.988", +//! # "PERIAPSIS": "472.664", +//! # "OBJECT_TYPE": "PAYLOAD", +//! # "RCS_SIZE": "MEDIUM", +//! # "COUNTRY_CODE": "ARGN", +//! # "LAUNCH_DATE": "2020-01-15", +//! # "SITE": "TSC", +//! # "DECAY_DATE": null, +//! # "FILE": "2911831", +//! # "GP_ID": "168552672", +//! # "TLE_LINE0": "0 NUSAT-8 (MARIE)", +//! # "TLE_LINE1": "1 45018U 20003C 20364.16527091 .00002241 00000-0 84553-4 0 9997", +//! # "TLE_LINE2": "2 45018 97.2970 66.4161 0013356 110.6345 334.7107 15.27989249 53274" +//! # }"#; +//! # +//! let message: OmmType = serde_json::de::from_str(json).unwrap(); +//! ``` +//! +//! To deserialize a list of JSON messages: +//! +//! ``` +//! # use lox_io::ndm::json::omm::OmmType; +//! # let json = r#"[{ +//! # "CCSDS_OMM_VERS": "2.0", +//! # "COMMENT": "GENERATED VIA SPACE-TRACK.ORG API", +//! # "CREATION_DATE": "2020-12-29T06:26:10", +//! # "ORIGINATOR": "18 SPCS", +//! # "OBJECT_NAME": "NUSAT-8 (MARIE)", +//! # "OBJECT_ID": "2020-003C", +//! # "CENTER_NAME": "EARTH", +//! # "REF_FRAME": "TEME", +//! # "TIME_SYSTEM": "UTC", +//! # "MEAN_ELEMENT_THEORY": "SGP4", +//! # "EPOCH": "2020-12-29T03:57:59.406624", +//! # "MEAN_MOTION": "15.27989249", +//! # "ECCENTRICITY": "0.00133560", +//! # "INCLINATION": "97.2970", +//! # "RA_OF_ASC_NODE": "66.4161", +//! # "ARG_OF_PERICENTER": "110.6345", +//! # "MEAN_ANOMALY": "334.7107", +//! # "EPHEMERIS_TYPE": "0", +//! # "CLASSIFICATION_TYPE": "U", +//! # "NORAD_CAT_ID": "45018", +//! # "ELEMENT_SET_NO": "999", +//! # "REV_AT_EPOCH": "5327", +//! # "BSTAR": "0.00008455300000", +//! # "MEAN_MOTION_DOT": "0.00002241", +//! # "MEAN_MOTION_DDOT": "0.0000000000000", +//! # "SEMIMAJOR_AXIS": "6859.961", +//! # "PERIOD": "94.242", +//! # "APOAPSIS": "490.988", +//! # "PERIAPSIS": "472.664", +//! # "OBJECT_TYPE": "PAYLOAD", +//! # "RCS_SIZE": "MEDIUM", +//! # "COUNTRY_CODE": "ARGN", +//! # "LAUNCH_DATE": "2020-01-15", +//! # "SITE": "TSC", +//! # "DECAY_DATE": null, +//! # "FILE": "2911831", +//! # "GP_ID": "168552672", +//! # "TLE_LINE0": "0 NUSAT-8 (MARIE)", +//! # "TLE_LINE1": "1 45018U 20003C 20364.16527091 .00002241 00000-0 84553-4 0 9997", +//! # "TLE_LINE2": "2 45018 97.2970 66.4161 0013356 110.6345 334.7107 15.27989249 53274" +//! # }]"#; +//! # +//! let message: Vec = serde_json::de::from_str(json).unwrap(); +//! ``` + +use serde_aux::prelude::*; +use std::collections::HashMap; + +#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct SpacecraftParametersType { + #[serde( + rename = "MASS", + deserialize_with = "deserialize_option_number_from_string" + )] + pub mass: Option, + #[serde( + rename = "SOLAR_RAD_AREA", + deserialize_with = "deserialize_option_number_from_string" + )] + pub solar_rad_area: Option, + #[serde( + rename = "SOLAR_RAD_COEFF", + deserialize_with = "deserialize_option_number_from_string" + )] + pub solar_rad_coeff: Option, + #[serde( + rename = "DRAG_AREA", + deserialize_with = "deserialize_option_number_from_string" + )] + pub drag_area: Option, + #[serde( + rename = "DRAG_COEFF", + deserialize_with = "deserialize_option_number_from_string" + )] + pub drag_coeff: Option, +} + +#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct MeanElementsType { + #[serde(rename = "EPOCH")] + pub epoch: String, + #[serde(rename = "SEMI_MAJOR_AXIS")] + pub semi_major_axis: Option, + #[serde( + rename = "MEAN_MOTION", + deserialize_with = "deserialize_number_from_string" + )] + pub mean_motion: f64, + #[serde( + rename = "ECCENTRICITY", + deserialize_with = "deserialize_number_from_string" + )] + pub eccentricity: f64, + #[serde( + rename = "INCLINATION", + deserialize_with = "deserialize_number_from_string" + )] + pub inclination: f64, + #[serde( + rename = "RA_OF_ASC_NODE", + deserialize_with = "deserialize_number_from_string" + )] + pub ra_of_asc_node: f64, + #[serde( + rename = "ARG_OF_PERICENTER", + deserialize_with = "deserialize_number_from_string" + )] + pub arg_of_pericenter: f64, + #[serde( + rename = "MEAN_ANOMALY", + deserialize_with = "deserialize_number_from_string" + )] + pub mean_anomaly: f64, + #[serde( + rename = "GM", + deserialize_with = "deserialize_option_number_from_string" + )] + pub gm: Option, +} + +#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct TleParametersType { + #[serde( + rename = "EPHEMERIS_TYPE", + deserialize_with = "deserialize_option_number_from_string" + )] + pub ephemeris_type: Option, + #[serde(rename = "CLASSIFICATION_TYPE")] + pub classification_type: Option, + #[serde( + rename = "NORAD_CAT_ID", + deserialize_with = "deserialize_option_number_from_string" + )] + pub norad_cat_id: Option, + #[serde( + rename = "ELEMENT_SET_NO", + deserialize_with = "deserialize_option_number_from_string" + )] + pub element_set_no: Option, + #[serde( + rename = "REV_AT_EPOCH", + deserialize_with = "deserialize_option_number_from_string" + )] + pub rev_at_epoch: Option, + #[serde( + rename = "BSTAR", + deserialize_with = "deserialize_option_number_from_string" + )] + pub bstar: Option, + #[serde( + rename = "BTERM", + deserialize_with = "deserialize_option_number_from_string" + )] + pub bterm: Option, + #[serde( + rename = "MEAN_MOTION_DOT", + deserialize_with = "deserialize_number_from_string" + )] + pub mean_motion_dot: f64, + #[serde( + rename = "MEAN_MOTION_DDOT", + deserialize_with = "deserialize_option_number_from_string" + )] + pub mean_motion_ddot: Option, + #[serde( + rename = "AGOM", + deserialize_with = "deserialize_option_number_from_string" + )] + pub agom: Option, +} + +#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct OpmCovarianceMatrixType { + #[serde(rename = "COV_REF_FRAME")] + pub cov_ref_frame: Option, + #[serde(rename = "CX_X", deserialize_with = "deserialize_number_from_string")] + pub cx_x: f64, + #[serde(rename = "CY_X", deserialize_with = "deserialize_number_from_string")] + pub cy_x: f64, + #[serde(rename = "CY_Y", deserialize_with = "deserialize_number_from_string")] + pub cy_y: f64, + #[serde(rename = "CZ_X", deserialize_with = "deserialize_number_from_string")] + pub cz_x: f64, + #[serde(rename = "CZ_Y", deserialize_with = "deserialize_number_from_string")] + pub cz_y: f64, + #[serde(rename = "CZ_Z", deserialize_with = "deserialize_number_from_string")] + pub cz_z: f64, + #[serde( + rename = "CX_DOT_X", + deserialize_with = "deserialize_number_from_string" + )] + pub cx_dot_x: f64, + #[serde( + rename = "CX_DOT_Y", + deserialize_with = "deserialize_number_from_string" + )] + pub cx_dot_y: f64, + #[serde( + rename = "CX_DOT_Z", + deserialize_with = "deserialize_number_from_string" + )] + pub cx_dot_z: f64, + #[serde( + rename = "CX_DOT_X_DOT", + deserialize_with = "deserialize_number_from_string" + )] + pub cx_dot_x_dot: f64, + #[serde( + rename = "CY_DOT_X", + deserialize_with = "deserialize_number_from_string" + )] + pub cy_dot_x: f64, + #[serde( + rename = "CY_DOT_Y", + deserialize_with = "deserialize_number_from_string" + )] + pub cy_dot_y: f64, + #[serde( + rename = "CY_DOT_Z", + deserialize_with = "deserialize_number_from_string" + )] + pub cy_dot_z: f64, + #[serde( + rename = "CY_DOT_X_DOT", + deserialize_with = "deserialize_number_from_string" + )] + pub cy_dot_x_dot: f64, + #[serde( + rename = "CY_DOT_Y_DOT", + deserialize_with = "deserialize_number_from_string" + )] + pub cy_dot_y_dot: f64, + #[serde( + rename = "CZ_DOT_X", + deserialize_with = "deserialize_number_from_string" + )] + pub cz_dot_x: f64, + #[serde( + rename = "CZ_DOT_Y", + deserialize_with = "deserialize_number_from_string" + )] + pub cz_dot_y: f64, + #[serde( + rename = "CZ_DOT_Z", + deserialize_with = "deserialize_number_from_string" + )] + pub cz_dot_z: f64, + #[serde( + rename = "CZ_DOT_X_DOT", + deserialize_with = "deserialize_number_from_string" + )] + pub cz_dot_x_dot: f64, + #[serde( + rename = "CZ_DOT_Y_DOT", + deserialize_with = "deserialize_number_from_string" + )] + pub cz_dot_y_dot: f64, + #[serde( + rename = "CZ_DOT_Z_DOT", + deserialize_with = "deserialize_number_from_string" + )] + pub cz_dot_z_dot: f64, +} + +#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct OmmMetadata { + #[serde(rename = "COMMENT")] + pub comment_list: Vec, + #[serde(rename = "OBJECT_NAME")] + pub object_name: String, + #[serde(rename = "OBJECT_ID")] + pub object_id: String, + #[serde(rename = "CENTER_NAME")] + pub center_name: String, + #[serde(rename = "REF_FRAME")] + pub ref_frame: String, + #[serde(rename = "REF_FRAME_EPOCH")] + pub ref_frame_epoch: Option, + #[serde(rename = "TIME_SYSTEM")] + pub time_system: String, + #[serde(rename = "MEAN_ELEMENT_THEORY")] + pub mean_element_theory: String, +} + +#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct OdmHeader { + #[serde(rename = "COMMENT")] + pub comment_list: String, + #[serde(rename = "CLASSIFICATION")] + pub classification_list: Vec, + #[serde(rename = "CREATION_DATE")] + pub creation_date: String, + #[serde(rename = "ORIGINATOR")] + pub originator: String, + #[serde(rename = "MESSAGE_ID")] + pub message_id: Option, +} + +#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct OmmType { + #[serde(flatten)] + pub header: OdmHeader, + + #[serde(flatten)] + pub metadata: OmmMetadata, + + #[serde(flatten)] + pub mean_elements: MeanElementsType, + #[serde(flatten)] + pub spacecraft_parameters: Option, + #[serde(flatten)] + pub tle_parameters: Option, + #[serde(flatten)] + pub covariance_matrix: Option, + + #[serde(flatten)] + pub user_defined_parameters: HashMap>, +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_parse_json() { + let json = r#"[ + { + "CCSDS_OMM_VERS": "2.0", + "COMMENT": "GENERATED VIA SPACE-TRACK.ORG API", + "CREATION_DATE": "2020-12-29T06:26:10", + "ORIGINATOR": "18 SPCS", + "OBJECT_NAME": "NUSAT-8 (MARIE)", + "OBJECT_ID": "2020-003C", + "CENTER_NAME": "EARTH", + "REF_FRAME": "TEME", + "TIME_SYSTEM": "UTC", + "MEAN_ELEMENT_THEORY": "SGP4", + "EPOCH": "2020-12-29T03:57:59.406624", + "MEAN_MOTION": "15.27989249", + "ECCENTRICITY": "0.00133560", + "INCLINATION": "97.2970", + "RA_OF_ASC_NODE": "66.4161", + "ARG_OF_PERICENTER": "110.6345", + "MEAN_ANOMALY": "334.7107", + "EPHEMERIS_TYPE": "0", + "CLASSIFICATION_TYPE": "U", + "NORAD_CAT_ID": "45018", + "ELEMENT_SET_NO": "999", + "REV_AT_EPOCH": "5327", + "BSTAR": "0.00008455300000", + "MEAN_MOTION_DOT": "0.00002241", + "MEAN_MOTION_DDOT": "0.0000000000000", + "SEMIMAJOR_AXIS": "6859.961", + "PERIOD": "94.242", + "APOAPSIS": "490.988", + "PERIAPSIS": "472.664", + "OBJECT_TYPE": "PAYLOAD", + "RCS_SIZE": "MEDIUM", + "COUNTRY_CODE": "ARGN", + "LAUNCH_DATE": "2020-01-15", + "SITE": "TSC", + "DECAY_DATE": null, + "FILE": "2911831", + "GP_ID": "168552672", + "TLE_LINE0": "0 NUSAT-8 (MARIE)", + "TLE_LINE1": "1 45018U 20003C 20364.16527091 .00002241 00000-0 84553-4 0 9997", + "TLE_LINE2": "2 45018 97.2970 66.4161 0013356 110.6345 334.7107 15.27989249 53274" + } +]"#; + + let message: Vec = serde_json::de::from_str(json).unwrap(); + + assert_eq!( + message, + [OmmType { + header: OdmHeader { + comment_list: "GENERATED VIA SPACE-TRACK.ORG API".to_string(), + classification_list: vec![], + creation_date: "2020-12-29T06:26:10".to_string(), + originator: "18 SPCS".to_string(), + message_id: None, + }, + metadata: OmmMetadata { + comment_list: vec![], + object_name: "NUSAT-8 (MARIE)".to_string(), + object_id: "2020-003C".to_string(), + center_name: "EARTH".to_string(), + ref_frame: "TEME".to_string(), + ref_frame_epoch: None, + time_system: "UTC".to_string(), + mean_element_theory: "SGP4".to_string(), + }, + mean_elements: MeanElementsType { + epoch: "2020-12-29T03:57:59.406624".to_string(), + semi_major_axis: None, + mean_motion: 15.27989249, + eccentricity: 0.0013356, + inclination: 97.297, + ra_of_asc_node: 66.4161, + arg_of_pericenter: 110.6345, + mean_anomaly: 334.7107, + gm: None, + }, + spacecraft_parameters: Some(SpacecraftParametersType { + mass: None, + solar_rad_area: None, + solar_rad_coeff: None, + drag_area: None, + drag_coeff: None, + },), + tle_parameters: Some(TleParametersType { + ephemeris_type: Some(0,), + classification_type: Some("U".to_string()), + norad_cat_id: Some(45018,), + element_set_no: Some(999,), + rev_at_epoch: Some(5327,), + bstar: Some(8.4553e-5,), + bterm: None, + mean_motion_dot: 2.241e-5, + mean_motion_ddot: Some(0.0,), + agom: None, + },), + covariance_matrix: Some(OpmCovarianceMatrixType { + cov_ref_frame: None, + cx_x: 0.0, + cy_x: 0.0, + cy_y: 0.0, + cz_x: 0.0, + cz_y: 0.0, + cz_z: 0.0, + cx_dot_x: 0.0, + cx_dot_y: 0.0, + cx_dot_z: 0.0, + cx_dot_x_dot: 0.0, + cy_dot_x: 0.0, + cy_dot_y: 0.0, + cy_dot_z: 0.0, + cy_dot_x_dot: 0.0, + cy_dot_y_dot: 0.0, + cz_dot_x: 0.0, + cz_dot_y: 0.0, + cz_dot_z: 0.0, + cz_dot_x_dot: 0.0, + cz_dot_y_dot: 0.0, + cz_dot_z_dot: 0.0, + },), + user_defined_parameters: HashMap::from([ + ("RCS_SIZE".to_string(), Some("MEDIUM".to_string(),)), + ( + "TLE_LINE2".to_string(), + Some( + "2 45018 97.2970 66.4161 0013356 110.6345 334.7107 15.27989249 53274" + .to_string(), + ) + ), + ("SITE".to_string(), Some("TSC".to_string(),)), + ("LAUNCH_DATE".to_string(), Some("2020-01-15".to_string(),)), + ("CCSDS_OMM_VERS".to_string(), Some("2.0".to_string(),)), + ("DECAY_DATE".to_string(), None), + ("FILE".to_string(), Some("2911831".to_string(),)), + ("GP_ID".to_string(), Some("168552672".to_string(),)), + ("OBJECT_TYPE".to_string(), Some("PAYLOAD".to_string(),)), + ("APOAPSIS".to_string(), Some("490.988".to_string(),)), + ( + "TLE_LINE0".to_string(), + Some("0 NUSAT-8 (MARIE)".to_string(),) + ), + ("PERIOD".to_string(), Some("94.242".to_string(),)), + ("SEMIMAJOR_AXIS".to_string(), Some("6859.961".to_string(),)), + ("COUNTRY_CODE".to_string(), Some("ARGN".to_string(),)), + ( + "TLE_LINE1".to_string(), + Some( + "1 45018U 20003C 20364.16527091 .00002241 00000-0 84553-4 0 9997" + .to_string(), + ) + ), + ("PERIAPSIS".to_string(), Some("472.664".to_string(),)), + ]), + },] + ); + } +} diff --git a/crates/lox-io/src/ndm/kvn.rs b/crates/lox-io/src/ndm/kvn.rs new file mode 100644 index 00000000..848dbdc5 --- /dev/null +++ b/crates/lox-io/src/ndm/kvn.rs @@ -0,0 +1,6 @@ +//! The public interface for the `KvnDeserializer` type + +mod deserializer; +pub(crate) mod parser; + +pub use deserializer::{KvnDeserializer, KvnDeserializerErr}; diff --git a/crates/lox-io/src/ndm/kvn/deserializer.rs b/crates/lox-io/src/ndm/kvn/deserializer.rs new file mode 100644 index 00000000..2268be88 --- /dev/null +++ b/crates/lox-io/src/ndm/kvn/deserializer.rs @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023. Helge Eichhorn and the LOX contributors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + */ + +use nom::error::ErrorKind; + +pub trait KvnDeserializer { + fn deserialize<'a>( + lines: &mut std::iter::Peekable>, + ) -> Result> + where + Self: Sized; + + fn from_kvn_str(kvn: &str) -> Result> + where + Self: Sized, + { + Self::deserialize(&mut kvn.lines().peekable()) + } + + fn should_check_key_match() -> bool; +} + +#[derive(PartialEq, Clone, thiserror::Error, Debug)] +pub enum KvnDeserializerErr { + InvalidDateTimeFormat { input: I }, + InvalidNumberFormat { input: I }, + InvalidStringFormat { input: I }, + KeywordNotFound { expected: I }, + UnexpectedKeyword { found: I, expected: I }, + EmptyKeyword { input: I }, + EmptyValue { input: I }, + UnexpectedEndOfInput { keyword: I }, + GeneralParserError(I, ErrorKind), +} diff --git a/crates/lox-io/src/ndm/kvn/parser.rs b/crates/lox-io/src/ndm/kvn/parser.rs new file mode 100644 index 00000000..fc00c331 --- /dev/null +++ b/crates/lox-io/src/ndm/kvn/parser.rs @@ -0,0 +1,1030 @@ +/* + * Copyright (c) 2023. Helge Eichhorn and the LOX contributors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + */ + +// This parser handles the Keyword Value Notation (KVN) defined in section +// 7.4 of CCSDS 502.0-B-3 (https://public.ccsds.org/Pubs/502x0b3e1.pdf). + +use nom::bytes::complete as nb; +use nom::character::complete as nc; +use nom::error::{ErrorKind, ParseError}; +use nom::sequence as ns; +use regex::Regex; + +use super::deserializer::KvnDeserializerErr; + +#[derive(Debug, PartialEq)] +pub enum KvnStringParserErr { + EmptyKeyword { input: I }, + EmptyValue { input: I }, + InvalidFormat { input: I }, + ParserError(I, ErrorKind), +} + +#[derive(Debug, PartialEq)] +pub struct KvnKeywordNotFoundErr { + expected: I, +} + +#[derive(PartialEq, Debug)] +pub enum KvnNumberParserErr { + ParserError(I, ErrorKind), + EmptyKeyword { input: I }, + EmptyValue { input: I }, + InvalidFormat { input: I }, +} + +#[derive(PartialEq, Debug)] +pub enum KvnDateTimeParserErr { + ParserError(I, ErrorKind), + EmptyKeyword { input: I }, + EmptyValue { input: I }, + InvalidFormat { input: I }, +} + +impl From>> for KvnDeserializerErr { + fn from(value: nom::Err>) -> Self { + match value { + nom::Err::Error(KvnStringParserErr::EmptyValue { input }) + | nom::Err::Failure(KvnStringParserErr::EmptyValue { input }) => { + KvnDeserializerErr::EmptyValue { + input: input.to_string(), + } + } + nom::Err::Error(KvnStringParserErr::EmptyKeyword { input }) + | nom::Err::Failure(KvnStringParserErr::EmptyKeyword { input }) => { + KvnDeserializerErr::EmptyKeyword { + input: input.to_string(), + } + } + nom::Err::Error(KvnStringParserErr::InvalidFormat { input }) + | nom::Err::Failure(KvnStringParserErr::InvalidFormat { input }) => { + KvnDeserializerErr::InvalidStringFormat { + input: input.to_string(), + } + } + nom::Err::Error(KvnStringParserErr::ParserError(i, k)) + | nom::Err::Failure(KvnStringParserErr::ParserError(i, k)) => { + KvnDeserializerErr::GeneralParserError(i.to_string(), k) + } + // We don't use streaming deserialization + nom::Err::Incomplete(_) => unimplemented!(), + } + } +} + +impl From>> for KvnDeserializerErr { + fn from(value: nom::Err>) -> Self { + match value { + nom::Err::Error(KvnDateTimeParserErr::EmptyValue { input }) + | nom::Err::Failure(KvnDateTimeParserErr::EmptyValue { input }) => { + KvnDeserializerErr::EmptyValue { + input: input.to_string(), + } + } + nom::Err::Error(KvnDateTimeParserErr::EmptyKeyword { input }) + | nom::Err::Failure(KvnDateTimeParserErr::EmptyKeyword { input }) => { + KvnDeserializerErr::EmptyKeyword { + input: input.to_string(), + } + } + nom::Err::Error(KvnDateTimeParserErr::InvalidFormat { input }) + | nom::Err::Failure(KvnDateTimeParserErr::InvalidFormat { input }) => { + KvnDeserializerErr::InvalidDateTimeFormat { + input: input.to_string(), + } + } + nom::Err::Error(KvnDateTimeParserErr::ParserError(i, k)) + | nom::Err::Failure(KvnDateTimeParserErr::ParserError(i, k)) => { + KvnDeserializerErr::GeneralParserError(i.to_string(), k) + } + // We don't use streaming deserialization + nom::Err::Incomplete(_) => unimplemented!(), + } + } +} + +impl From>> for KvnDeserializerErr { + fn from(value: nom::Err>) -> Self { + match value { + nom::Err::Error(KvnNumberParserErr::EmptyValue { input }) + | nom::Err::Failure(KvnNumberParserErr::EmptyValue { input }) => { + KvnDeserializerErr::EmptyValue { + input: input.to_string(), + } + } + nom::Err::Error(KvnNumberParserErr::EmptyKeyword { input }) + | nom::Err::Failure(KvnNumberParserErr::EmptyKeyword { input }) => { + KvnDeserializerErr::EmptyKeyword { + input: input.to_string(), + } + } + nom::Err::Error(KvnNumberParserErr::InvalidFormat { input }) + | nom::Err::Failure(KvnNumberParserErr::InvalidFormat { input }) => { + KvnDeserializerErr::InvalidDateTimeFormat { + input: input.to_string(), + } + } + nom::Err::Error(KvnNumberParserErr::ParserError(i, k)) + | nom::Err::Failure(KvnNumberParserErr::ParserError(i, k)) => { + KvnDeserializerErr::GeneralParserError(i.to_string(), k) + } + // We don't use streaming deserialization + nom::Err::Incomplete(_) => unimplemented!(), + } + } +} + +impl From> for KvnDeserializerErr { + fn from(value: KvnKeywordNotFoundErr<&str>) -> Self { + KvnDeserializerErr::KeywordNotFound { + expected: value.expected.to_string(), + } + } +} + +impl ParseError for KvnStringParserErr { + fn from_error_kind(input: I, kind: ErrorKind) -> Self { + KvnStringParserErr::ParserError(input, kind) + } + + fn append(_: I, _: ErrorKind, other: Self) -> Self { + other + } +} + +#[derive(PartialEq, Debug, Default)] +pub struct KvnValue { + pub value: V, + pub unit: Option, +} + +#[derive(PartialEq, Debug, Default)] +pub struct KvnDateTimeValue { + pub year: u16, + pub month: u8, + pub day: u8, + pub hour: u8, + pub minute: u8, + pub second: u8, + pub fractional_second: f64, + pub full_value: String, +} + +//@TODO remove _new suffix +pub fn kvn_line_matches_key_new<'a>( + key: &'a str, + input: &'a str, +) -> Result> { + if key == "COMMENT" { + ns::tuple((nc::space0::<_, nom::error::Error<_>>, nb::tag(key)))(input) + .map(|_| true) + .or(Ok(false)) + } else { + let mut equals = ns::tuple((nc::space0::<_, nom::error::Error<_>>, nc::char('='))); + + if equals(input).is_ok() { + return Err(KvnKeywordNotFoundErr { expected: key }); + } + + ns::delimited(nc::space0, nb::tag(key), equals)(input) + .map(|_| true) + .or(Ok(false)) + } +} + +pub fn parse_kvn_string_line_new( + input: &str, +) -> nom::IResult<&str, KvnValue, KvnStringParserErr<&str>> { + if input.trim_start().starts_with("COMMENT ") { + return Ok(( + "", + KvnValue { + value: input + .trim_start() + .trim_start_matches("COMMENT") + .trim_start() + .to_string(), + unit: None, + }, + )); + } + + if is_empty_value(input) { + Err(nom::Err::Failure(KvnStringParserErr::EmptyValue { input }))? + }; + + // Inspired by figure F-8: CCSDS 502.0-B-3, but accepts a more relaxed input. Orekit seems to suggest that there + // are quite a few messages being used which are not strictly compliant. + let re = + Regex::new(r"^(?:\s*)(?[0-9A-Z_]*)(?:\s*)=(?:\s*)(?(?:(?:.*)))(?:\s*)$") + .unwrap(); + + let captures = + re.captures(input) + .ok_or(nom::Err::Failure(KvnStringParserErr::InvalidFormat { + input, + }))?; + + let keyword = captures + .name("keyword") + // This unwrap is okay because the keyword uses * so it will always capture + .unwrap() + .as_str() + .trim_end() + .to_string(); + + if keyword.is_empty() { + return Err(nom::Err::Failure(KvnStringParserErr::EmptyKeyword { + input, + })); + } + + let value = captures + .name("value") + // This unwrap is okay because the value uses * so it will always capture + .unwrap() + .as_str() + .trim_end() + .to_string(); + + if value.is_empty() { + return Err(nom::Err::Failure(KvnStringParserErr::EmptyValue { input })); + } + + Ok(("", KvnValue { value, unit: None })) +} + +pub fn parse_kvn_integer_line_new( + input: &str, + with_unit: bool, +) -> nom::IResult<&str, KvnValue, KvnNumberParserErr<&str>> +where + T: std::str::FromStr, +{ + if is_empty_value(input) { + Err(nom::Err::Failure(KvnNumberParserErr::EmptyValue { input }))? + }; + + let regex_pattern = if with_unit { + r"^(?:\s*)(?[0-9A-Za-z_]*)(?:\s*)=(?:\s*)(?(?:[-+]?)(?:[0-9]+)(?:\.\d*)?)(?:(?:\s*)(?:\[(?[0-9A-Za-z/_*]*)\]?))?(?:\s*)?$" + } else { + r"^(?:\s*)(?[0-9A-Za-z_]*)(?:\s*)=(?:\s*)(?(?:[-+]?)(?:[0-9]+)(?:\.\d*)?)(?:\s*)$" + }; + + // Modified from Figure F-9: CCSDS 502.0-B-3 + let re = Regex::new(regex_pattern).unwrap(); + + let captures = + re.captures(input) + .ok_or(nom::Err::Failure(KvnNumberParserErr::InvalidFormat { + input, + }))?; + + let keyword = captures + .name("keyword") + // This unwrap is okay because the keyword is marked as * so it will always capture + .unwrap() + .as_str() + .trim_end() + .to_string(); + + if keyword.is_empty() { + return Err(nom::Err::Failure(KvnNumberParserErr::EmptyKeyword { + input, + })); + } + + // This unwrap is okay because the value uses * so it will always capture + let value = captures.name("value").unwrap().as_str(); + let unit = captures.name("unit").map(|x| x.as_str().to_string()); + + let value = value + .parse::() + .map_err(|_| nom::Err::Failure(KvnNumberParserErr::InvalidFormat { input }))?; + + Ok(("", KvnValue { value, unit })) +} + +fn is_empty_value(input: &str) -> bool { + let re = Regex::new( + r"^(?:\s*)(?[0-9A-Za-z_]*)(?:\s*)=(?:\s*)(?:\[(?[0-9A-Za-z/_*]*)\]?)?$", + ) + .unwrap(); + + re.is_match(input) +} + +pub fn parse_kvn_numeric_line_new( + input: &str, + with_unit: bool, +) -> nom::IResult<&str, KvnValue, KvnNumberParserErr<&str>> { + if is_empty_value(input) { + Err(nom::Err::Failure(KvnNumberParserErr::EmptyValue { input }))? + }; + + let regex_pattern = if with_unit { + // Figure F-9: CCSDS 502.0-B-3 + r"^(?:\s*)(?[0-9A-Za-z_]*)(?:\s*)=(?:\s*)(?(?:[-+]?)(?:[0-9]+)(?:\.\d*)?(?:[eE][+-]?(?:\d+))?)(?:(?:\s*)(?:\[(?[0-9A-Za-z/_*]*)\]?))?(?:\s*)?$" + } else { + r"^(?:\s*)(?[0-9A-Za-z_]*)(?:\s*)=(?:\s*)(?(?:[-+]?)(?:[0-9]+)(?:\.\d*)?(?:[eE][+-]?(?:\d+))?)(?:\s*)?$" + }; + + let re = Regex::new(regex_pattern).unwrap(); + + let captures = + re.captures(input) + .ok_or(nom::Err::Failure(KvnNumberParserErr::InvalidFormat { + input, + }))?; + + let keyword = captures + .name("keyword") + // This unwrap is okay because the keyword is marked as * so it will always capture + .unwrap() + .as_str() + .trim_end() + .to_string(); + + if keyword.is_empty() { + return Err(nom::Err::Failure(KvnNumberParserErr::EmptyKeyword { + input, + })); + } + + // This unwrap is okay because the value uses * so it will always capture + let value = captures.name("value").unwrap().as_str(); + let unit = captures.name("unit").map(|x| x.as_str().to_string()); + + let value = fast_float::parse(value) + .map_err(|_| nom::Err::Failure(KvnNumberParserErr::InvalidFormat { input }))?; + + Ok(("", KvnValue { value, unit })) +} + +pub fn parse_kvn_datetime_line_new( + input: &str, +) -> nom::IResult<&str, KvnDateTimeValue, KvnDateTimeParserErr<&str>> { + if is_empty_value(input) { + Err(nom::Err::Failure(KvnDateTimeParserErr::EmptyValue { + input, + }))? + }; + + // Modified from Figure F-5: CCSDS 502.0-B-3 + let re = Regex::new(r"^(?:\s*)?(?[0-9A-Z_]*)(?:\s*)?=(?:\s*)?(?(?(?:\d{4}))-(?(?:\d{1,2}))-(?(?:\d{1,2}))T(?
(?:\d{1,2})):(?(?:\d{1,2})):(?(?:\d{0,2}(?:\.\d*)?)))(?:\s*)?$").unwrap(); + + let captures = + re.captures(input) + .ok_or(nom::Err::Failure(KvnDateTimeParserErr::InvalidFormat { + input, + }))?; + + let keyword = captures + // This unwrap is okay because the keyword is marked as * so it will always capture + .name("keyword") + .unwrap() + .as_str() + .trim_end() + .to_string(); + + if keyword.is_empty() { + return Err(nom::Err::Failure(KvnDateTimeParserErr::EmptyKeyword { + input, + })); + } + + // yr is a mandatory decimal in the regex so we expect the capture to be + // always there and unwrap is fine + let year = captures + .name("yr") + .unwrap() + .as_str() + .parse::() + .unwrap(); + + // We don't do full validation of the date values. We only care if they + // have the expected number of digits + + // mo is a mandatory decimal in the regex so we expect the capture to be + // always there and unwrap is fine + let month = captures.name("mo").unwrap().as_str().parse::().unwrap(); + + // day is a mandatory decimal in the regex so we expect the capture to be + // always there and unwrap is fine + let day = captures.name("dy").unwrap().as_str().parse::().unwrap(); + + // hr is a mandatory decimal in the regex so we expect the capture to be + // always there and unwrap is fine + let hour = captures.name("hr").unwrap().as_str().parse::().unwrap(); + + // mn is a mandatory decimal in the regex so we expect the capture to be + // always there and unwrap is fine + let minute = captures.name("mn").unwrap().as_str().parse::().unwrap(); + + // sc is a mandatory decimal in the regex so we expect the capture to be + // always there and unwrap is fine + let full_second = captures + .name("sc") + .unwrap() + .as_str() + .parse::() + .unwrap(); + + let second = full_second.floor() as u8; + + let fractional_second = full_second.fract(); + + let full_value = captures.name("value").unwrap().as_str().to_string(); + + Ok(( + "", + KvnDateTimeValue { + year, + month, + day, + hour, + minute, + second, + fractional_second, + full_value, + }, + )) +} + +#[cfg(test)] +mod test { + use lox_derive::KvnDeserialize; + + use super::*; + + #[test] + fn test_kvn_line_matches_key_new() { + assert_eq!( + kvn_line_matches_key_new("ASD", "AAAAAD123 = ASDFG"), + Ok(false) + ); + assert_eq!(kvn_line_matches_key_new("ASD", "ASD = ASDFG"), Ok(true)); + + assert_eq!( + kvn_line_matches_key_new("ASD", "ASD ASD = ASDFG"), + Ok(false) + ); + assert_eq!( + kvn_line_matches_key_new("ASD", "= ASDFG"), + Err(KvnKeywordNotFoundErr { expected: "ASD" }) + ); + } + + #[test] + fn test_parse_kvn_string_line_new() { + // 7.5.1 A non-empty value field must be assigned to each mandatory keyword except for *‘_START’ and *‘_STOP’ keyword values + // 7.4.6 Any white space immediately preceding or following the ‘equals’ sign shall not be significant. + assert_eq!( + parse_kvn_string_line_new("ASD = ASDFG"), + Ok(( + "", + KvnValue { + value: "ASDFG".to_string(), + unit: None + } + )) + ); + assert_eq!( + parse_kvn_string_line_new("ASD = ASDFG"), + Ok(( + "", + KvnValue { + value: "ASDFG".to_string(), + unit: None + } + )) + ); + assert_eq!( + parse_kvn_string_line_new("ASD = ASDFG"), + Ok(( + "", + KvnValue { + value: "ASDFG".to_string(), + unit: None + } + )) + ); + assert_eq!( + parse_kvn_string_line_new("ASD = "), + Err(nom::Err::Failure(KvnStringParserErr::EmptyValue { + input: "ASD = " + })) + ); + assert_eq!( + parse_kvn_string_line_new("ASD = "), + Err(nom::Err::Failure(KvnStringParserErr::EmptyValue { + input: "ASD = " + })) + ); + assert_eq!( + parse_kvn_string_line_new("ASD ="), + Err(nom::Err::Failure(KvnStringParserErr::EmptyValue { + input: "ASD =" + })) + ); + + assert_eq!( + parse_kvn_string_line_new("ASD [km]"), + Err(nom::Err::Failure(KvnStringParserErr::InvalidFormat { + input: "ASD [km]" + })) + ); + assert_eq!( + parse_kvn_string_line_new(" = asd [km]"), + Err(nom::Err::Failure(KvnStringParserErr::EmptyKeyword { + input: " = asd [km]" + })) + ); + + // 7.4.7 Any white space immediately preceding the end of line shall not be significant. + assert_eq!( + parse_kvn_string_line_new("ASD = ASDFG "), + Ok(( + "", + KvnValue { + value: "ASDFG".to_string(), + unit: None + } + )) + ); + + // 7.4.5 Any white space immediately preceding or following the keyword shall not be significant. + assert_eq!( + parse_kvn_string_line_new(" ASD = ASDFG"), + Ok(( + "", + KvnValue { + value: "ASDFG".to_string(), + unit: None + } + )) + ); + + // 7.8.5 All comment lines shall begin with the ‘COMMENT’ keyword followed by at least one space. + // [...] White space shall be retained (shall be significant) in comment values. + + assert_eq!( + parse_kvn_string_line_new(" COMMENT asd a asd a ads as "), + Ok(( + "", + KvnValue { + value: "asd a asd a ads as ".to_string(), + unit: None + } + )) + ); + + assert_eq!( + parse_kvn_string_line_new(" COMMENT "), + Ok(( + "", + KvnValue { + value: "".to_string(), + unit: None + } + )) + ); + } + + #[test] + fn test_parse_kvn_integer_line_new() { + // a) there must be at least one blank character between the value and the units text; + // b) the units must be enclosed within square brackets (e.g., ‘[m]’); + assert_eq!( + parse_kvn_integer_line_new("SCLK_OFFSET_AT_EPOCH = 28800 [s]", true), + Ok(( + "", + KvnValue { + value: 28800, + unit: Some("s".to_string()) + }, + )) + ); + + // 7.4.7 Any white space immediately preceding the end of line shall not be significant. + + assert_eq!( + parse_kvn_integer_line_new("SCLK_OFFSET_AT_EPOCH = 28800 [s]", true), + Ok(( + "", + KvnValue { + value: 28800, + unit: Some("s".to_string()) + } + )) + ); + + assert_eq!( + parse_kvn_integer_line_new("SCLK_OFFSET_AT_EPOCH = 28800 ", false), + Ok(( + "", + KvnValue { + value: 28800, + unit: None + } + )) + ); + + // 7.4.5 Any white space immediately preceding or following the keyword shall not be significant. + + assert_eq!( + parse_kvn_integer_line_new(" SCLK_OFFSET_AT_EPOCH = 28800", false), + Ok(( + "", + KvnValue { + value: 28800, + unit: None + } + )) + ); + + assert_eq!( + parse_kvn_integer_line_new("SCLK_OFFSET_AT_EPOCH = 00028800 [s]", true), + Ok(( + "", + KvnValue { + value: 28800, + unit: Some("s".to_string()) + }, + )) + ); + + assert_eq!( + parse_kvn_integer_line_new("SCLK_OFFSET_AT_EPOCH = -28800 [s]", true), + Ok(( + "", + KvnValue { + value: -28800, + unit: Some("s".to_string()) + }, + )) + ); + + assert_eq!( + parse_kvn_integer_line_new("SCLK_OFFSET_AT_EPOCH = -28800", true), + Ok(( + "", + KvnValue { + value: -28800, + unit: None + }, + )) + ); + + assert_eq!( + parse_kvn_integer_line_new("SCLK_OFFSET_AT_EPOCH = 28800 [s]", true), + Ok(( + "", + KvnValue { + value: 28800, + unit: Some("s".to_string()) + }, + )) + ); + + assert_eq!( + parse_kvn_integer_line_new::("SCLK_OFFSET_AT_EPOCH = 28800 [s]", false), + Err(nom::Err::Failure(KvnNumberParserErr::InvalidFormat { + input: "SCLK_OFFSET_AT_EPOCH = 28800 [s]" + })) + ); + + assert_eq!( + parse_kvn_integer_line_new::("SCLK_OFFSET_AT_EPOCH = -asd", true), + Err(nom::Err::Failure(KvnNumberParserErr::InvalidFormat { + input: "SCLK_OFFSET_AT_EPOCH = -asd" + })) + ); + + assert_eq!( + parse_kvn_integer_line_new::("SCLK_OFFSET_AT_EPOCH = [s]", true), + Err(nom::Err::Failure(KvnNumberParserErr::EmptyValue { + input: "SCLK_OFFSET_AT_EPOCH = [s]" + })) + ); + + assert_eq!( + parse_kvn_integer_line_new::("SCLK_OFFSET_AT_EPOCH = ", false), + Err(nom::Err::Failure(KvnNumberParserErr::EmptyValue { + input: "SCLK_OFFSET_AT_EPOCH = " + })) + ); + assert_eq!( + parse_kvn_integer_line_new::("SCLK_OFFSET_AT_EPOCH = ", false), + Err(nom::Err::Failure(KvnNumberParserErr::EmptyValue { + input: "SCLK_OFFSET_AT_EPOCH = " + })) + ); + assert_eq!( + parse_kvn_integer_line_new::("SCLK_OFFSET_AT_EPOCH =", false), + Err(nom::Err::Failure(KvnNumberParserErr::EmptyValue { + input: "SCLK_OFFSET_AT_EPOCH =" + })) + ); + + assert_eq!( + parse_kvn_integer_line_new::("SCLK_OFFSET_AT_EPOCH [km]", true), + Err(nom::Err::Failure(KvnNumberParserErr::InvalidFormat { + input: "SCLK_OFFSET_AT_EPOCH [km]" + })) + ); + assert_eq!( + parse_kvn_integer_line_new::(" = 123 [km]", true), + Err(nom::Err::Failure(KvnNumberParserErr::EmptyKeyword { + input: " = 123 [km]" + })) + ); + } + + #[test] + fn test_parse_kvn_numeric_line_new() { + // a) there must be at least one blank character between the value and the units text; + // b) the units must be enclosed within square brackets (e.g., ‘[m]’); + assert_eq!( + parse_kvn_numeric_line_new("X = 66559942 [km]", true), + Ok(( + "", + KvnValue { + value: 66559942f64, + unit: Some("km".to_string()) + }, + )) + ); + + // 7.4.7 Any white space immediately preceding the end of line shall not be significant. + + assert_eq!( + parse_kvn_numeric_line_new("X = 66559942 [km]", true), + Ok(( + "", + KvnValue { + value: 66559942f64, + unit: Some("km".to_string()) + } + )) + ); + + assert_eq!( + parse_kvn_numeric_line_new("X = 66559942 ", false), + Ok(( + "", + KvnValue { + value: 66559942f64, + unit: None + } + )) + ); + + // 7.4.5 Any white space immediately preceding or following the keyword shall not be significant. + + assert_eq!( + parse_kvn_numeric_line_new(" X = 66559942", false), + Ok(( + "", + KvnValue { + value: 66559942f64, + unit: None + } + )) + ); + + assert_eq!( + parse_kvn_numeric_line_new("X = 6655.9942 [km]", true), + Ok(( + "", + KvnValue { + value: 6655.9942, + unit: Some("km".to_string()) + }, + )) + ); + + assert_eq!( + parse_kvn_numeric_line_new("CX_X = 5.801003223606e-05", true), + Ok(( + "", + KvnValue { + value: 5.801003223606e-05, + unit: None + }, + )) + ); + + assert_eq!( + parse_kvn_numeric_line_new("X = -asd", true), + Err(nom::Err::Failure(KvnNumberParserErr::InvalidFormat { + input: "X = -asd" + })) + ); + + assert_eq!( + parse_kvn_numeric_line_new("X = [s]", true), + Err(nom::Err::Failure(KvnNumberParserErr::EmptyValue { + input: "X = [s]" + })) + ); + + assert_eq!( + parse_kvn_numeric_line_new("X = ", false), + Err(nom::Err::Failure(KvnNumberParserErr::EmptyValue { + input: "X = " + })) + ); + assert_eq!( + parse_kvn_numeric_line_new("X = ", false), + Err(nom::Err::Failure(KvnNumberParserErr::EmptyValue { + input: "X = " + })) + ); + assert_eq!( + parse_kvn_numeric_line_new("X =", false), + Err(nom::Err::Failure(KvnNumberParserErr::EmptyValue { + input: "X =" + })) + ); + + assert_eq!( + parse_kvn_numeric_line_new("X [km]", true), + Err(nom::Err::Failure(KvnNumberParserErr::InvalidFormat { + input: "X [km]" + })) + ); + assert_eq!( + parse_kvn_numeric_line_new(" = 123 [km]", true), + Err(nom::Err::Failure(KvnNumberParserErr::EmptyKeyword { + input: " = 123 [km]" + })) + ); + } + + #[test] + fn test_parse_kvn_datetime_line_new() { + assert_eq!( + parse_kvn_datetime_line_new("CREATION_DATE = 2021-06-03T05:33:00.123"), + Ok(( + "", + KvnDateTimeValue { + year: 2021, + month: 6, + day: 3, + hour: 5, + minute: 33, + second: 0, + fractional_second: 0.123, + full_value: "2021-06-03T05:33:00.123".to_string(), + }, + )) + ); + + assert_eq!( + parse_kvn_datetime_line_new("CREATION_DATE = 2021-06-03T05:33:01"), + Ok(( + "", + KvnDateTimeValue { + year: 2021, + month: 6, + day: 3, + hour: 5, + minute: 33, + second: 1, + fractional_second: 0.0, + full_value: "2021-06-03T05:33:01".to_string(), + }, + )) + ); + + // 7.4.7 Any white space immediately preceding the end of line shall not be significant. + + assert_eq!( + parse_kvn_datetime_line_new("CREATION_DATE = 2021-06-03T05:33:01 "), + Ok(( + "", + KvnDateTimeValue { + year: 2021, + month: 6, + day: 3, + hour: 5, + minute: 33, + second: 1, + fractional_second: 0.0, + full_value: "2021-06-03T05:33:01".to_string(), + }, + )) + ); + + // 7.4.5 Any white space immediately preceding or following the keyword shall not be significant. + + assert_eq!( + parse_kvn_datetime_line_new(" CREATION_DATE = 2021-06-03T05:33:01"), + Ok(( + "", + KvnDateTimeValue { + year: 2021, + month: 6, + day: 3, + hour: 5, + minute: 33, + second: 1, + fractional_second: 0.0, + full_value: "2021-06-03T05:33:01".to_string(), + }, + )) + ); + + // @TODO add support for ddd format + + assert_eq!( + parse_kvn_datetime_line_new("CREATION_DATE = 2021,06,03Q05!33!00-123"), + Err(nom::Err::Failure(KvnDateTimeParserErr::InvalidFormat { + input: "CREATION_DATE = 2021,06,03Q05!33!00-123" + })) + ); + + assert_eq!( + parse_kvn_datetime_line_new("CREATION_DATE = asdffggg"), + Err(nom::Err::Failure(KvnDateTimeParserErr::InvalidFormat { + input: "CREATION_DATE = asdffggg" + })) + ); + + assert_eq!( + parse_kvn_datetime_line_new("CREATION_DATE = "), + Err(nom::Err::Failure(KvnDateTimeParserErr::EmptyValue { + input: "CREATION_DATE = " + })) + ); + + assert_eq!( + parse_kvn_datetime_line_new("CREATION_DATE = "), + Err(nom::Err::Failure(KvnDateTimeParserErr::EmptyValue { + input: "CREATION_DATE = " + })) + ); + + assert_eq!( + parse_kvn_datetime_line_new("CREATION_DATE ="), + Err(nom::Err::Failure(KvnDateTimeParserErr::EmptyValue { + input: "CREATION_DATE =" + })) + ); + + assert_eq!( + parse_kvn_datetime_line_new("CREATION_DATE "), + Err(nom::Err::Failure(KvnDateTimeParserErr::InvalidFormat { + input: "CREATION_DATE " + })) + ); + assert_eq!( + parse_kvn_datetime_line_new(" = 2021-06-03T05:33:01"), + Err(nom::Err::Failure(KvnDateTimeParserErr::EmptyKeyword { + input: " = 2021-06-03T05:33:01" + })) + ); + } + + #[derive(Default, Debug, PartialEq)] + pub struct PositionUnits(pub std::string::String); + + #[derive(KvnDeserialize, Default, Debug, PartialEq)] + #[kvn(value_unit_struct)] + pub struct DistanceType { + pub base: f64, + pub units: Option, + } + + #[derive(KvnDeserialize, Default, Debug, PartialEq)] + struct AsdType { + pub version: String, + pub semi_major_axis: DistanceType, + pub asdfg: f64, + } + + #[test] + fn test_parse_with_unit_struct() { + let kvn = r#"CCSDS_ASD_VERS = 3.0 + SEMI_MAJOR_AXIS = 41399.5123 [km] + ASDFG = 12333.5123"#; + + assert_eq!( + crate::ndm::kvn::KvnDeserializer::deserialize(&mut kvn.lines().peekable()), + Ok(AsdType { + semi_major_axis: DistanceType { + base: 41399.5123, + units: Some(PositionUnits("km".to_string(),)), + }, + asdfg: 12333.5123f64, + version: "3.0".to_string(), + },) + ) + } +} diff --git a/crates/lox-io/src/ndm/ndm_ci.rs b/crates/lox-io/src/ndm/ndm_ci.rs new file mode 100644 index 00000000..72af4311 --- /dev/null +++ b/crates/lox-io/src/ndm/ndm_ci.rs @@ -0,0 +1,1334 @@ +/* + * Copyright (c) 2023. Helge Eichhorn and the LOX contributors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + */ + +//! Deserializers for XML CCSDS Navigation Data Message Combined Instantiation +//! +//! To deserialize an XML message: +//! +//! ``` +//! # let xml = r#" +//! # bla +//! # asdfg +//! # +//! #
+//! # +//! # +//! #
+//! # +//! # +//! # +//! # +//! #
+//! # +//! #
+//! # 2004-281T17:26:06 +//! # me +//! #
+//! # +//! # +//! # +//! # Cassini +//! # 1997-061A +//! # Saturn +//! # IAU-Saturn +//! # UTC +//! # +//! # +//! # +//! # this is a comment +//! # 2004-100T00:00:00 +//! # 1 +//! # 1 +//! # 1 +//! # 1 +//! # 1 +//! # 1 +//! # +//! # +//! # This is a COMMENT +//! # 100 +//! # 2 +//! # 1 +//! # 2 +//! # 2.0 +//! # +//! # +//! # This is a COMMENT +//! # 2004-125T00:00:00 +//! # 0 +//! # -1 +//! # GRC +//! # 1 +//! # 1 +//! # 1 +//! # +//! # +//! # +//! # +//! #
+//! # +//! # +//! # +//! #
+//! # 2004-281T17:26:06 +//! # me +//! #
+//! # +//! # +//! # +//! # Cassini +//! # 1997-061A +//! # Saturn +//! # IAU-Saturn +//! # UTC +//! # 2004-100T00:00:00.000000 +//! # 2004-100T01:00:00.000000 +//! # Hermite +//! # 1 +//! # +//! # +//! # +//! # 2004-100T00:00:00 +//! # 1 +//! # 1 +//! # 1 +//! # 1 +//! # 1 +//! # 1 +//! # +//! # +//! # 2004-100T00:00:00 +//! # 1 +//! # 1 +//! # 1 +//! # 1 +//! # 1 +//! # 1 +//! # +//! # +//! # 2004-100T00:00:00 +//! # 1 +//! # 1 +//! # 1 +//! # 1 +//! # 1 +//! # 1 +//! # +//! # +//! # +//! # +//! #
+//! # +//! #
+//! # +//! # +//! #
+//! # +//! # +//! # +//! # NUSAT-13 (EMMY) +//! # 2020-079G +//! # EARTH +//! # TEME +//! # UTC +//! # SGP4 +//! # +//! # +//! # +//! # 2020-12-04T13:30:01.539648 +//! # 15.31433655 +//! # .0009574 +//! # 97.2663 +//! # 51.2167 +//! # 149.8567 +//! # 322.5146 +//! # +//! # +//! # 0 +//! # U +//! # 46833 +//! # 999 +//! # 434 +//! # .14401E-3 +//! # 4.301E-5 +//! # 0 +//! # +//! # +//! # +//! # +//! #
+//! # +//! #
"#; +//! # +//! # use lox_io::ndm::ndm_ci::NdmType; +//! use lox_io::ndm::xml::FromXmlStr; +//! +//! let message = NdmType::from_xml_str(xml).unwrap(); +//! ``` + +// This file is partially generated with xml-schema-derive from the XSD schema +// published by CCSDS. Adaptations have been made to simplify the types or +// allow to simplify the implementation of the KVN parser. + +use serde; + +use super::{ocm, oem, omm, opm}; + +#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde()] +#[allow(clippy::large_enum_variant)] +pub enum NdmChildChoice { + #[serde(rename = "ocm")] + Ocm(ocm::OcmType), + + #[serde(rename = "oem")] + Oem(oem::OemType), + + #[serde(rename = "omm")] + Omm(omm::OmmType), + + #[serde(rename = "opm")] + Opm(opm::OpmType), +} + +/// Combined instantiation type. Currently does not support AEM, APM, CDM, +/// RDM, TDM +#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct NdmType { + #[serde(rename = "MESSAGE_ID")] + pub message_id: Option, + #[serde(rename = "COMMENT")] + pub comment_list: Vec, + + #[serde(rename = "$value")] + pub child_list: Vec, +} + +impl crate::ndm::xml::FromXmlStr<'_> for NdmType {} + +#[cfg(test)] +mod test { + use crate::ndm::xml::FromXmlStr; + + use super::super::common; + use super::*; + + #[test] + fn test_parse_combined_ndm() { + let xml = r#" +bla +asdfg + +
+ + +
+ + + + +
+ +
+ + +
+ + + + NUSAT-2 (BATATA) + 2016-033C + EARTH + TEME + UTC + SGP4 + + + + 2020-12-04T15:27:01.975104 + 15.30610336 + .0011780 + 97.4090 + 71.7453 + 193.9419 + 272.1492 + + + 0 + U + 41558 + 999 + 25191 + .15913E-3 + 4.64E-5 + 0 + + + + +
+ +
+ 2004-281T17:26:06 + me +
+ + + + Cassini + 1997-061A + Saturn + IAU-Saturn + UTC + + + final COMMENT, I think + + 2004-100T00:00:00Z + 1 + 1 + 1 + 1 + 1 + 1 + + + 1 + 0 + 45 + 0 + 15 + 15 + 398644 + + + 100 + 2 + 1 + 2 + 2.0 + + + 2004-125T00:00:00Z + 0 + -1 + GRC + 1 + 1 + 1 + + + + +
+ + +
+ 2004-281T17:26:06 + me +
+ + + + Cassini + 1997-061A + Saturn + IAU-Saturn + UTC + + + + this is a comment + 2004-100T00:00:00 + 1 + 1 + 1 + 1 + 1 + 1 + + + This is a COMMENT + 100 + 2 + 1 + 2 + 2.0 + + + This is a COMMENT + 2004-125T00:00:00 + 0 + -1 + GRC + 1 + 1 + 1 + + + + +
+ + + +
+ 2004-281T17:26:06 + me +
+ + + + Cassini + 1997-061A + Saturn + IAU-Saturn + UTC + 2004-100T00:00:00.000000 + 2004-100T01:00:00.000000 + Hermite + 1 + + + + 2004-100T00:00:00 + 1 + 1 + 1 + 1 + 1 + 1 + + + 2004-100T00:00:00 + 1 + 1 + 1 + 1 + 1 + 1 + + + 2004-100T00:00:00 + 1 + 1 + 1 + 1 + 1 + 1 + + + + +
+ +
+ + +
+ + + + NUSAT-13 (EMMY) + 2020-079G + EARTH + TEME + UTC + SGP4 + + + + 2020-12-04T13:30:01.539648 + 15.31433655 + .0009574 + 97.2663 + 51.2167 + 149.8567 + 322.5146 + + + 0 + U + 46833 + 999 + 434 + .14401E-3 + 4.301E-5 + 0 + + + + +
+ +
+ + +
+ + + + NUSAT-17 (MARY) + 2020-079J + EARTH + TEME + UTC + SGP4 + + + + 2020-12-04T16:27:08.698176 + 15.31317097 + .0009674 + 97.2671 + 51.3486 + 160.8608 + 302.2789 + + + 0 + U + 46835 + 999 + 436 + -.13273E-3 + -4.087E-5 + 0 + + + + +
+ +
+ + +
+ + + + NUSAT-18 (VERA) + 2020-079K + EARTH + TEME + UTC + SGP4 + + + + 2020-12-04T13:13:33.140064 + 15.32037173 + .0009024 + 97.2666 + 51.2301 + 167.2057 + 304.5569 + + + 0 + U + 46836 + 999 + 434 + .13328E-3 + 4.05E-5 + 0 + + + + +
+ +
"#; + + let message = NdmType::from_xml_str(xml).unwrap(); + + assert_eq!( + message, + NdmType { + message_id: Some("bla".to_string()), + comment_list: vec!["asdfg".to_string()], + child_list: vec![ + NdmChildChoice::Omm(omm::OmmType { + header: common::OdmHeader { + comment_list: vec![], + classification_list: vec![], + creation_date: common::EpochType("".to_string()), + originator: "".to_string(), + message_id: None, + }, + body: omm::OmmBody { + segment: omm::OmmSegment { + metadata: omm::OmmMetadata { + comment_list: vec![], + object_name: "".to_string(), + object_id: "".to_string(), + center_name: "".to_string(), + ref_frame: "".to_string(), + ref_frame_epoch: None, + time_system: "".to_string(), + mean_element_theory: "".to_string(), + }, + data: omm::OmmData { + comment_list: vec![], + mean_elements: omm::MeanElementsType { + comment_list: vec![], + epoch: common::EpochType("".to_string()), + semi_major_axis: None, + mean_motion: None, + eccentricity: common::NonNegativeDouble(0.0), + inclination: common::InclinationType { + base: 0.0, + units: None, + }, + ra_of_asc_node: common::AngleType { + base: 0.0, + units: None, + }, + arg_of_pericenter: common::AngleType { + base: 0.0, + units: None, + }, + mean_anomaly: common::AngleType { + base: 0.0, + units: None, + }, + gm: None, + }, + spacecraft_parameters: None, + tle_parameters: None, + covariance_matrix: None, + user_defined_parameters: None, + }, + }, + }, + id: Some("CCSDS_OMM_VERS".to_string()), + version: "2.0".to_string(), + }), + NdmChildChoice::Omm(omm::OmmType { + header: common::OdmHeader { + comment_list: vec![], + classification_list: vec![], + creation_date: common::EpochType("".to_string()), + originator: "".to_string(), + message_id: None, + }, + body: omm::OmmBody { + segment: omm::OmmSegment { + metadata: omm::OmmMetadata { + comment_list: vec![], + object_name: "NUSAT-2 (BATATA)".to_string(), + object_id: "2016-033C".to_string(), + center_name: "EARTH".to_string(), + ref_frame: "TEME".to_string(), + ref_frame_epoch: None, + time_system: "UTC".to_string(), + mean_element_theory: "SGP4".to_string(), + }, + data: omm::OmmData { + comment_list: vec![], + mean_elements: omm::MeanElementsType { + comment_list: vec![], + epoch: common::EpochType( + "2020-12-04T15:27:01.975104".to_string() + ), + semi_major_axis: None, + mean_motion: Some(omm::RevType { + base: 15.30610336, + units: None, + }), + eccentricity: common::NonNegativeDouble(0.001178), + inclination: common::InclinationType { + base: 97.409, + units: None, + }, + ra_of_asc_node: common::AngleType { + base: 71.7453, + units: None, + }, + arg_of_pericenter: common::AngleType { + base: 193.9419, + units: None, + }, + mean_anomaly: common::AngleType { + base: 272.1492, + units: None, + }, + gm: None, + }, + spacecraft_parameters: None, + tle_parameters: Some(omm::TleParametersType { + comment_list: vec![], + ephemeris_type: Some(0), + classification_type: Some("U".to_string()), + norad_cat_id: Some(41558), + element_set_no: Some(omm::ElementSetNoType( + "999".to_string() + )), + rev_at_epoch: Some(25191), + bstar: Some(omm::BStarType { + base: 0.00015913, + units: None, + }), + bterm: None, + mean_motion_dot: omm::DRevType { + base: 4.64e-5, + units: None, + }, + mean_motion_ddot: Some(omm::DRevType { + base: 0.0, + units: None, + }), + agom: None, + }), + covariance_matrix: None, + user_defined_parameters: None, + }, + }, + }, + id: Some("CCSDS_OMM_VERS".to_string()), + version: "2.0".to_string(), + }), + NdmChildChoice::Opm(opm::OpmType { + header: common::OdmHeader { + comment_list: vec![], + classification_list: vec![], + creation_date: common::EpochType("2004-281T17:26:06".to_string()), + originator: "me".to_string(), + message_id: None, + }, + body: opm::OpmBody { + segment: opm::OpmSegment { + metadata: opm::OpmMetadata { + comment_list: vec![], + object_name: "Cassini".to_string(), + object_id: "1997-061A".to_string(), + center_name: "Saturn".to_string(), + ref_frame: "IAU-Saturn".to_string(), + ref_frame_epoch: None, + time_system: "UTC".to_string(), + }, + data: opm::OpmData { + comment_list: vec!["final COMMENT, I think".to_string()], + state_vector: common::StateVectorType { + comment_list: vec![], + epoch: common::EpochType("2004-100T00:00:00Z".to_string()), + x: common::PositionType { + base: 1.0, + units: None, + }, + y: common::PositionType { + base: 1.0, + units: None, + }, + z: common::PositionType { + base: 1.0, + units: None, + }, + x_dot: common::VelocityType { + base: 1.0, + units: None, + }, + y_dot: common::VelocityType { + base: 1.0, + units: None, + }, + z_dot: common::VelocityType { + base: 1.0, + units: None, + }, + }, + keplerian_elements: Some(opm::KeplerianElementsType { + comment_list: vec![], + semi_major_axis: common::DistanceType { + base: 1.0, + units: Some(common::PositionUnits("km".to_string())), + }, + eccentricity: common::NonNegativeDouble(0.0,), + inclination: common::InclinationType { + base: 45.0, + units: Some(common::AngleUnits("deg".to_string())), + }, + ra_of_asc_node: common::AngleType { + base: 0.0, + units: Some(common::AngleUnits("deg".to_string())), + }, + arg_of_pericenter: common::AngleType { + base: 15.0, + units: Some(common::AngleUnits("deg".to_string())), + }, + true_anomaly: Some(common::AngleType { + base: 15.0, + units: Some(common::AngleUnits("deg".to_string())), + },), + mean_anomaly: None, + gm: common::GmType { + base: common::PositiveDouble(398644.0,), + units: Some(common::GmUnits("km**3/s**2".to_string())), + }, + },), + spacecraft_parameters: Some(common::SpacecraftParametersType { + comment_list: vec![], + mass: Some(common::MassType { + base: common::NonNegativeDouble(100.0,), + units: Some(common::MassUnits("kg".to_string())), + },), + solar_rad_area: Some(common::AreaType { + base: common::NonNegativeDouble(2.0,), + units: Some(common::AreaUnits("m**2".to_string())), + },), + solar_rad_coeff: Some(common::NonNegativeDouble(1.0,)), + drag_area: Some(common::AreaType { + base: common::NonNegativeDouble(2.0,), + units: Some(common::AreaUnits("m**2".to_string())), + },), + drag_coeff: Some(common::NonNegativeDouble(2.0,)), + },), + covariance_matrix: None, + maneuver_parameters_list: vec![opm::ManeuverParametersType { + comment_list: vec![], + man_epoch_ignition: common::EpochType( + "2004-125T00:00:00Z".to_string(), + ), + man_duration: common::DurationType { + base: common::NonNegativeDouble(0.0,), + units: None, + }, + man_delta_mass: common::DeltamassType { + base: common::NegativeDouble(-1.0,), + units: None, + }, + man_ref_frame: "GRC".to_string(), + man_dv_1: common::VelocityType { + base: 1.0, + units: Some(common::VelocityUnits("km/s".to_string())), + }, + man_dv_2: common::VelocityType { + base: 1.0, + units: Some(common::VelocityUnits("km/s".to_string())), + }, + man_dv_3: common::VelocityType { + base: 1.0, + units: Some(common::VelocityUnits("km/s".to_string())), + }, + },], + user_defined_parameters: None, + }, + }, + }, + id: Some("CCSDS_OPM_VERS".to_string()), + version: "2.0".to_string(), + },), + NdmChildChoice::Opm(opm::OpmType { + header: common::OdmHeader { + comment_list: vec![], + classification_list: vec![], + creation_date: common::EpochType("2004-281T17:26:06".to_string()), + originator: "me".to_string(), + message_id: None, + }, + body: opm::OpmBody { + segment: opm::OpmSegment { + metadata: opm::OpmMetadata { + comment_list: vec![], + object_name: "Cassini".to_string(), + object_id: "1997-061A".to_string(), + center_name: "Saturn".to_string(), + ref_frame: "IAU-Saturn".to_string(), + ref_frame_epoch: None, + time_system: "UTC".to_string(), + }, + data: opm::OpmData { + comment_list: vec![], + state_vector: common::StateVectorType { + comment_list: vec!["this is a comment".to_string()], + epoch: common::EpochType("2004-100T00:00:00".to_string()), + x: common::PositionType { + base: 1.0, + units: None, + }, + y: common::PositionType { + base: 1.0, + units: None, + }, + z: common::PositionType { + base: 1.0, + units: None, + }, + x_dot: common::VelocityType { + base: 1.0, + units: None, + }, + y_dot: common::VelocityType { + base: 1.0, + units: None, + }, + z_dot: common::VelocityType { + base: 1.0, + units: None, + }, + }, + keplerian_elements: None, + spacecraft_parameters: Some(common::SpacecraftParametersType { + comment_list: vec!["This is a COMMENT".to_string()], + mass: Some(common::MassType { + base: common::NonNegativeDouble(100.0,), + units: None, + },), + solar_rad_area: Some(common::AreaType { + base: common::NonNegativeDouble(2.0,), + units: None, + },), + solar_rad_coeff: Some(common::NonNegativeDouble(1.0,)), + drag_area: Some(common::AreaType { + base: common::NonNegativeDouble(2.0,), + units: Some(common::AreaUnits("m**2".to_string())), + },), + drag_coeff: Some(common::NonNegativeDouble(2.0,)), + },), + covariance_matrix: None, + maneuver_parameters_list: vec![opm::ManeuverParametersType { + comment_list: vec!["This is a COMMENT".to_string()], + man_epoch_ignition: common::EpochType( + "2004-125T00:00:00".to_string(), + ), + man_duration: common::DurationType { + base: common::NonNegativeDouble(0.0,), + units: Some(common::TimeUnits("s".to_string())), + }, + man_delta_mass: common::DeltamassType { + base: common::NegativeDouble(-1.0,), + units: Some(common::MassUnits("kg".to_string())), + }, + man_ref_frame: "GRC".to_string(), + man_dv_1: common::VelocityType { + base: 1.0, + units: None, + }, + man_dv_2: common::VelocityType { + base: 1.0, + units: None, + }, + man_dv_3: common::VelocityType { + base: 1.0, + units: None, + }, + },], + user_defined_parameters: None, + }, + }, + }, + id: Some("CCSDS_OPM_VERS".to_string()), + version: "2.0".to_string(), + },), + NdmChildChoice::Oem(oem::OemType { + header: common::OdmHeader { + comment_list: vec![], + classification_list: vec![], + creation_date: common::EpochType("2004-281T17:26:06".to_string()), + originator: "me".to_string(), + message_id: None, + }, + body: oem::OemBody { + segment_list: vec![oem::OemSegment { + metadata: oem::OemMetadata { + comment_list: vec![], + object_name: "Cassini".to_string(), + object_id: "1997-061A".to_string(), + center_name: "Saturn".to_string(), + ref_frame: "IAU-Saturn".to_string(), + ref_frame_epoch: None, + time_system: "UTC".to_string(), + start_time: common::EpochType( + "2004-100T00:00:00.000000".to_string(), + ), + useable_start_time: None, + useable_stop_time: None, + stop_time: common::EpochType( + "2004-100T01:00:00.000000".to_string(), + ), + interpolation: Some("Hermite".to_string()), + interpolation_degree: Some(1,), + }, + data: oem::OemData { + comment_list: vec![], + state_vector_list: vec![ + common::StateVectorAccType { + epoch: common::EpochType( + "2004-100T00:00:00".to_string(), + ), + x: common::PositionType { + base: 1.0, + units: Some(common::PositionUnits( + "km".to_string(), + )), + }, + y: common::PositionType { + base: 1.0, + units: None, + }, + z: common::PositionType { + base: 1.0, + units: None, + }, + x_dot: common::VelocityType { + base: 1.0, + units: Some(common::VelocityUnits( + "km/s".to_string(), + )), + }, + y_dot: common::VelocityType { + base: 1.0, + units: None, + }, + z_dot: common::VelocityType { + base: 1.0, + units: None, + }, + x_ddot: None, + y_ddot: None, + z_ddot: None, + }, + common::StateVectorAccType { + epoch: common::EpochType( + "2004-100T00:00:00".to_string(), + ), + x: common::PositionType { + base: 1.0, + units: None, + }, + y: common::PositionType { + base: 1.0, + units: Some(common::PositionUnits( + "km".to_string(), + )), + }, + z: common::PositionType { + base: 1.0, + units: None, + }, + x_dot: common::VelocityType { + base: 1.0, + units: None, + }, + y_dot: common::VelocityType { + base: 1.0, + units: Some(common::VelocityUnits( + "km/s".to_string(), + )), + }, + z_dot: common::VelocityType { + base: 1.0, + units: None, + }, + x_ddot: None, + y_ddot: None, + z_ddot: None, + }, + common::StateVectorAccType { + epoch: common::EpochType( + "2004-100T00:00:00".to_string(), + ), + x: common::PositionType { + base: 1.0, + units: None, + }, + y: common::PositionType { + base: 1.0, + units: None, + }, + z: common::PositionType { + base: 1.0, + units: Some(common::PositionUnits( + "km".to_string(), + )), + }, + x_dot: common::VelocityType { + base: 1.0, + units: None, + }, + y_dot: common::VelocityType { + base: 1.0, + units: None, + }, + z_dot: common::VelocityType { + base: 1.0, + units: Some(common::VelocityUnits( + "km/s".to_string(), + )), + }, + x_ddot: None, + y_ddot: None, + z_ddot: None, + }, + ], + covariance_matrix_list: vec![], + }, + },], + }, + id: "CCSDS_OEM_VERS".to_string(), + version: "2.0".to_string(), + },), + NdmChildChoice::Omm(omm::OmmType { + header: common::OdmHeader { + comment_list: vec![], + classification_list: vec![], + creation_date: common::EpochType("".to_string()), + originator: "".to_string(), + message_id: None, + }, + body: omm::OmmBody { + segment: omm::OmmSegment { + metadata: omm::OmmMetadata { + comment_list: vec![], + object_name: "NUSAT-13 (EMMY)".to_string(), + object_id: "2020-079G".to_string(), + center_name: "EARTH".to_string(), + ref_frame: "TEME".to_string(), + ref_frame_epoch: None, + time_system: "UTC".to_string(), + mean_element_theory: "SGP4".to_string(), + }, + data: omm::OmmData { + comment_list: vec![], + mean_elements: omm::MeanElementsType { + comment_list: vec![], + epoch: common::EpochType( + "2020-12-04T13:30:01.539648".to_string() + ), + semi_major_axis: None, + mean_motion: Some(omm::RevType { + base: 15.31433655, + units: None, + }), + eccentricity: common::NonNegativeDouble(0.0009574), + inclination: common::InclinationType { + base: 97.2663, + units: None, + }, + ra_of_asc_node: common::AngleType { + base: 51.2167, + units: None, + }, + arg_of_pericenter: common::AngleType { + base: 149.8567, + units: None, + }, + mean_anomaly: common::AngleType { + base: 322.5146, + units: None, + }, + gm: None, + }, + spacecraft_parameters: None, + tle_parameters: Some(omm::TleParametersType { + comment_list: vec![], + ephemeris_type: Some(0), + classification_type: Some("U".to_string()), + norad_cat_id: Some(46833), + element_set_no: Some(omm::ElementSetNoType( + "999".to_string() + )), + rev_at_epoch: Some(434), + bstar: Some(omm::BStarType { + base: 0.00014401, + units: None, + }), + bterm: None, + mean_motion_dot: omm::DRevType { + base: 4.301e-5, + units: None, + }, + mean_motion_ddot: Some(omm::DRevType { + base: 0.0, + units: None, + }), + agom: None, + }), + covariance_matrix: None, + user_defined_parameters: None, + }, + }, + }, + id: Some("CCSDS_OMM_VERS".to_string()), + version: "2.0".to_string(), + }), + NdmChildChoice::Omm(omm::OmmType { + header: common::OdmHeader { + comment_list: vec![], + classification_list: vec![], + creation_date: common::EpochType("".to_string()), + originator: "".to_string(), + message_id: None, + }, + body: omm::OmmBody { + segment: omm::OmmSegment { + metadata: omm::OmmMetadata { + comment_list: vec![], + object_name: "NUSAT-17 (MARY)".to_string(), + object_id: "2020-079J".to_string(), + center_name: "EARTH".to_string(), + ref_frame: "TEME".to_string(), + ref_frame_epoch: None, + time_system: "UTC".to_string(), + mean_element_theory: "SGP4".to_string(), + }, + data: omm::OmmData { + comment_list: vec![], + mean_elements: omm::MeanElementsType { + comment_list: vec![], + epoch: common::EpochType( + "2020-12-04T16:27:08.698176".to_string() + ), + semi_major_axis: None, + mean_motion: Some(omm::RevType { + base: 15.31317097, + units: None, + }), + eccentricity: common::NonNegativeDouble(0.0009674), + inclination: common::InclinationType { + base: 97.2671, + units: None, + }, + ra_of_asc_node: common::AngleType { + base: 51.3486, + units: None, + }, + arg_of_pericenter: common::AngleType { + base: 160.8608, + units: None, + }, + mean_anomaly: common::AngleType { + base: 302.2789, + units: None, + }, + gm: None, + }, + spacecraft_parameters: None, + tle_parameters: Some(omm::TleParametersType { + comment_list: vec![], + ephemeris_type: Some(0), + classification_type: Some("U".to_string()), + norad_cat_id: Some(46835), + element_set_no: Some(omm::ElementSetNoType( + "999".to_string() + )), + rev_at_epoch: Some(436), + bstar: Some(omm::BStarType { + base: -0.00013273, + units: None, + }), + bterm: None, + mean_motion_dot: omm::DRevType { + base: -4.087e-5, + units: None, + }, + mean_motion_ddot: Some(omm::DRevType { + base: 0.0, + units: None, + }), + agom: None, + }), + covariance_matrix: None, + user_defined_parameters: None, + }, + }, + }, + id: Some("CCSDS_OMM_VERS".to_string()), + version: "2.0".to_string(), + }), + NdmChildChoice::Omm(omm::OmmType { + header: common::OdmHeader { + comment_list: vec![], + classification_list: vec![], + creation_date: common::EpochType("".to_string()), + originator: "".to_string(), + message_id: None, + }, + body: omm::OmmBody { + segment: omm::OmmSegment { + metadata: omm::OmmMetadata { + comment_list: vec![], + object_name: "NUSAT-18 (VERA)".to_string(), + object_id: "2020-079K".to_string(), + center_name: "EARTH".to_string(), + ref_frame: "TEME".to_string(), + ref_frame_epoch: None, + time_system: "UTC".to_string(), + mean_element_theory: "SGP4".to_string(), + }, + data: omm::OmmData { + comment_list: vec![], + mean_elements: omm::MeanElementsType { + comment_list: vec![], + epoch: common::EpochType( + "2020-12-04T13:13:33.140064".to_string() + ), + semi_major_axis: None, + mean_motion: Some(omm::RevType { + base: 15.32037173, + units: None, + }), + eccentricity: common::NonNegativeDouble(0.0009024), + inclination: common::InclinationType { + base: 97.2666, + units: None, + }, + ra_of_asc_node: common::AngleType { + base: 51.2301, + units: None, + }, + arg_of_pericenter: common::AngleType { + base: 167.2057, + units: None, + }, + mean_anomaly: common::AngleType { + base: 304.5569, + units: None, + }, + gm: None, + }, + spacecraft_parameters: None, + tle_parameters: Some(omm::TleParametersType { + comment_list: vec![], + ephemeris_type: Some(0), + classification_type: Some("U".to_string()), + norad_cat_id: Some(46836), + element_set_no: Some(omm::ElementSetNoType( + "999".to_string() + )), + rev_at_epoch: Some(434), + bstar: Some(omm::BStarType { + base: 0.00013328, + units: None, + }), + bterm: None, + mean_motion_dot: omm::DRevType { + base: 4.05e-5, + units: None, + }, + mean_motion_ddot: Some(omm::DRevType { + base: 0.0, + units: None, + }), + agom: None, + }), + covariance_matrix: None, + user_defined_parameters: None, + }, + }, + }, + id: Some("CCSDS_OMM_VERS".to_string()), + version: "2.0".to_string(), + }), + ], + }, + ); + } +} diff --git a/crates/lox-io/src/ndm/ocm.rs b/crates/lox-io/src/ndm/ocm.rs new file mode 100644 index 00000000..da18269f --- /dev/null +++ b/crates/lox-io/src/ndm/ocm.rs @@ -0,0 +1,1164 @@ +/* + * Copyright (c) 2023. Helge Eichhorn and the LOX contributors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + */ + +//! Deserializers for XML and KVN CCSDS Orbit Comprehensive Message +//! +//! To deserialize an XML message: +//! +//! ``` +//! # +//! # let xml = r#" +//! # +//! #
+//! # ODM V.3 Example G-2 +//! # OCM example with space object characteristics and perturbations. +//! # This OCM reflects the latest conditions post-maneuver A67Z +//! # This example shows the specification of multiple comment lines +//! # 1998-11-06T09:23:57 +//! # JAXA +//! # OCM 201113719185 +//! #
+//! # +//! # +//! # +//! # 1998-999A +//! # R. Rabbit +//! # Flight Dynamics Mission Design Lead +//! # (719)555-1234 +//! # Mr. Rodgers +//! # (719)555-1234 +//! # email@email.XXX +//! # UT1 +//! # 1998-12-18T00:00:00.0000 +//! # 36 +//! # .357 +//! # +//! # +//! # +//! # GEOCENTRIC, CARTESIAN, EARTH FIXED +//! # THIS IS MY SECOND COMMENT LINE +//! # PREDICTED +//! # EFG +//! # CARTPVA +//! # 0.0 2854.5 -2916.2 -5360.7 5.90 4.86 0.52 0.0037 -0.0038 -0.0070 +//! # +//! # +//! # Spacecraft Physical Characteristics +//! # 100.0 +//! # 0.03123 +//! # 0.78543 +//! # 0.39158 +//! # 0.47832 +//! # 2.0 +//! # 1.0 +//! # 0.5 +//! # 0.15 +//! # 0.3 +//! # 0.5 +//! # +//! # +//! # Perturbations Specification +//! # NRLMSIS00 +//! # EGM-96: 36D 36O +//! # 398600.4415 +//! # MOON, SUN +//! # 12.0 +//! # 105.0 +//! # 120.0 +//! # +//! # +//! # WGS-84 +//! # +//! # +//! # +//! # +//! #
"#; +//! # +//! # use lox_io::ndm::ocm::OcmType; +//! use lox_io::ndm::xml::FromXmlStr; +//! +//! let message = OcmType::from_xml_str(xml).unwrap(); +//! ``` + +// This file is partially generated with xml-schema-derive from the XSD schema +// published by CCSDS. Adaptations have been made to simplify the types or +// allow to simplify the implementation of the KVN parser. + +use serde; + +use super::common; + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct OcmType { + #[serde(rename = "header")] + pub header: common::OdmHeader, + #[serde(rename = "body")] + pub body: OcmBody, + #[serde(rename = "@id")] + pub id: String, + #[serde(rename = "@version")] + pub version: String, +} + +impl crate::ndm::xml::FromXmlStr<'_> for OcmType {} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct OcmBody { + #[serde(rename = "segment")] + pub segment: OcmSegment, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct OcmSegment { + #[serde(rename = "metadata")] + pub metadata: OcmMetadata, + #[serde(rename = "data")] + pub data: OcmData, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct OcmMetadata { + #[serde(rename = "COMMENT")] + pub comment_list: Vec, + #[serde(rename = "OBJECT_NAME")] + pub object_name: Option, + #[serde(rename = "INTERNATIONAL_DESIGNATOR")] + pub international_designator: Option, + #[serde(rename = "CATALOG_NAME")] + pub catalog_name: Option, + #[serde(rename = "OBJECT_DESIGNATOR")] + pub object_designator: Option, + #[serde(rename = "ALTERNATE_NAMES")] + pub alternate_names: Option, + #[serde(rename = "ORIGINATOR_POC")] + pub originator_poc: Option, + #[serde(rename = "ORIGINATOR_POSITION")] + pub originator_position: Option, + #[serde(rename = "ORIGINATOR_PHONE")] + pub originator_phone: Option, + #[serde(rename = "ORIGINATOR_EMAIL")] + pub originator_email: Option, + #[serde(rename = "ORIGINATOR_ADDRESS")] + pub originator_address: Option, + #[serde(rename = "TECH_ORG")] + pub tech_org: Option, + #[serde(rename = "TECH_POC")] + pub tech_poc: Option, + #[serde(rename = "TECH_POSITION")] + pub tech_position: Option, + #[serde(rename = "TECH_PHONE")] + pub tech_phone: Option, + #[serde(rename = "TECH_EMAIL")] + pub tech_email: Option, + #[serde(rename = "TECH_ADDRESS")] + pub tech_address: Option, + #[serde(rename = "PREVIOUS_MESSAGE_ID")] + pub previous_message_id: Option, + #[serde(rename = "NEXT_MESSAGE_ID")] + pub next_message_id: Option, + #[serde(rename = "ADM_MSG_LINK")] + pub adm_msg_link: Option, + #[serde(rename = "CDM_MSG_LINK")] + pub cdm_msg_link: Option, + #[serde(rename = "PRM_MSG_LINK")] + pub prm_msg_link: Option, + #[serde(rename = "RDM_MSG_LINK")] + pub rdm_msg_link: Option, + #[serde(rename = "TDM_MSG_LINK")] + pub tdm_msg_link: Option, + #[serde(rename = "OPERATOR")] + pub operator: Option, + #[serde(rename = "OWNER")] + pub owner: Option, + #[serde(rename = "COUNTRY")] + pub country: Option, + #[serde(rename = "CONSTELLATION")] + pub constellation: Option, + #[serde(rename = "OBJECT_TYPE")] + pub object_type: Option, + #[serde(rename = "TIME_SYSTEM")] + pub time_system: String, + #[serde(rename = "EPOCH_TZERO")] + pub epoch_tzero: common::EpochType, + #[serde(rename = "OPS_STATUS")] + pub ops_status: Option, + #[serde(rename = "ORBIT_CATEGORY")] + pub orbit_category: Option, + #[serde(rename = "OCM_DATA_ELEMENTS")] + pub ocm_data_elements: Option, + #[serde(rename = "SCLK_OFFSET_AT_EPOCH")] + pub sclk_offset_at_epoch: Option, + #[serde(rename = "SCLK_SEC_PER_SI_SEC")] + pub sclk_sec_per_si_sec: Option, + #[serde(rename = "PREVIOUS_MESSAGE_EPOCH")] + pub previous_message_epoch: Option, + #[serde(rename = "NEXT_MESSAGE_EPOCH")] + pub next_message_epoch: Option, + #[serde(rename = "START_TIME")] + pub start_time: Option, + #[serde(rename = "STOP_TIME")] + pub stop_time: Option, + #[serde(rename = "TIME_SPAN")] + pub time_span: Option, + #[serde(rename = "TAIMUTC_AT_TZERO")] + pub taimutc_at_tzero: Option, + #[serde(rename = "NEXT_LEAP_EPOCH")] + pub next_leap_epoch: Option, + #[serde(rename = "NEXT_LEAP_TAIMUTC")] + pub next_leap_taimutc: Option, + #[serde(rename = "UT1MUTC_AT_TZERO")] + pub ut1mutc_at_tzero: Option, + #[serde(rename = "EOP_SOURCE")] + pub eop_source: Option, + #[serde(rename = "INTERP_METHOD_EOP")] + pub interp_method_eop: Option, + #[serde(rename = "CELESTIAL_SOURCE")] + pub celestial_source: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct OcmData { + #[serde(rename = "traj")] + pub traj_list: Vec, + #[serde(rename = "phys")] + pub phys: Option, + #[serde(rename = "cov")] + pub cov_list: Vec, + #[serde(rename = "man")] + pub man_list: Vec, + #[serde(rename = "pert")] + pub pert: Option, + #[serde(rename = "od")] + pub od: Option, + #[serde(rename = "user")] + pub user: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct OcmTrajStateType { + #[serde(rename = "COMMENT")] + pub comment_list: Vec, + #[serde(rename = "TRAJ_ID")] + pub traj_id: Option, + #[serde(rename = "TRAJ_PREV_ID")] + pub traj_prev_id: Option, + #[serde(rename = "TRAJ_NEXT_ID")] + pub traj_next_id: Option, + #[serde(rename = "TRAJ_BASIS")] + pub traj_basis: Option, + #[serde(rename = "TRAJ_BASIS_ID")] + pub traj_basis_id: Option, + #[serde(rename = "INTERPOLATION")] + pub interpolation: Option, + #[serde(rename = "INTERPOLATION_DEGREE")] + pub interpolation_degree: Option, + #[serde(rename = "PROPAGATOR")] + pub propagator: Option, + #[serde(rename = "CENTER_NAME")] + pub center_name: String, + #[serde(rename = "TRAJ_REF_FRAME")] + pub traj_ref_frame: String, + #[serde(rename = "TRAJ_FRAME_EPOCH")] + pub traj_frame_epoch: Option, + #[serde(rename = "USEABLE_START_TIME")] + pub useable_start_time: Option, + #[serde(rename = "USEABLE_STOP_TIME")] + pub useable_stop_time: Option, + #[serde(rename = "ORB_REVNUM")] + pub orb_revnum: Option, + #[serde(rename = "ORB_REVNUM_BASIS")] + pub orb_revnum_basis: Option, + #[serde(rename = "TRAJ_TYPE")] + pub traj_type: String, + #[serde(rename = "ORB_AVERAGING")] + pub orb_averaging: Option, + #[serde(rename = "TRAJ_UNITS")] + pub traj_units: Option, + #[serde(rename = "trajLine")] + pub traj_line_list: Vec, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct OcmPhysicalDescriptionType { + #[serde(rename = "COMMENT")] + pub comment_list: Vec, + #[serde(rename = "MANUFACTURER")] + pub manufacturer: Option, + #[serde(rename = "BUS_MODEL")] + pub bus_model: Option, + #[serde(rename = "DOCKED_WITH")] + pub docked_with: Option, + #[serde(rename = "DRAG_CONST_AREA")] + pub drag_const_area: Option, + #[serde(rename = "DRAG_COEFF_NOM")] + pub drag_coeff_nom: Option, + #[serde(rename = "DRAG_UNCERTAINTY")] + pub drag_uncertainty: Option, + #[serde(rename = "INITIAL_WET_MASS")] + pub initial_wet_mass: Option, + #[serde(rename = "WET_MASS")] + pub wet_mass: Option, + #[serde(rename = "DRY_MASS")] + pub dry_mass: Option, + #[serde(rename = "OEB_PARENT_FRAME")] + pub oeb_parent_frame: Option, + #[serde(rename = "OEB_PARENT_FRAME_EPOCH")] + pub oeb_parent_frame_epoch: Option, + #[serde(rename = "OEB_Q1")] + pub oeb_q1: Option, + #[serde(rename = "OEB_Q2")] + pub oeb_q2: Option, + #[serde(rename = "OEB_Q3")] + pub oeb_q3: Option, + #[serde(rename = "OEB_QC")] + pub oeb_qc: Option, + #[serde(rename = "OEB_MAX")] + pub oeb_max: Option, + #[serde(rename = "OEB_INT")] + pub oeb_int: Option, + #[serde(rename = "OEB_MIN")] + pub oeb_min: Option, + #[serde(rename = "AREA_ALONG_OEB_MAX")] + pub area_along_oeb_max: Option, + #[serde(rename = "AREA_ALONG_OEB_INT")] + pub area_along_oeb_int: Option, + #[serde(rename = "AREA_ALONG_OEB_MIN")] + pub area_along_oeb_min: Option, + #[serde(rename = "AREA_MIN_FOR_PC")] + pub area_min_for_pc: Option, + #[serde(rename = "AREA_MAX_FOR_PC")] + pub area_max_for_pc: Option, + #[serde(rename = "AREA_TYP_FOR_PC")] + pub area_typ_for_pc: Option, + #[serde(rename = "RCS")] + pub rcs: Option, + #[serde(rename = "RCS_MIN")] + pub rcs_min: Option, + #[serde(rename = "RCS_MAX")] + pub rcs_max: Option, + #[serde(rename = "SRP_CONST_AREA")] + pub srp_const_area: Option, + #[serde(rename = "SOLAR_RAD_COEFF")] + pub solar_rad_coeff: Option, + #[serde(rename = "SOLAR_RAD_UNCERTAINTY")] + pub solar_rad_uncertainty: Option, + #[serde(rename = "VM_ABSOLUTE")] + pub vm_absolute: Option, + #[serde(rename = "VM_APPARENT_MIN")] + pub vm_apparent_min: Option, + #[serde(rename = "VM_APPARENT")] + pub vm_apparent: Option, + #[serde(rename = "VM_APPARENT_MAX")] + pub vm_apparent_max: Option, + #[serde(rename = "REFLECTANCE")] + pub reflectance: Option, + #[serde(rename = "ATT_CONTROL_MODE")] + pub att_control_mode: Option, + #[serde(rename = "ATT_ACTUATOR_TYPE")] + pub att_actuator_type: Option, + #[serde(rename = "ATT_KNOWLEDGE")] + pub att_knowledge: Option, + #[serde(rename = "ATT_CONTROL")] + pub att_control: Option, + #[serde(rename = "ATT_POINTING")] + pub att_pointing: Option, + #[serde(rename = "AVG_MANEUVER_FREQ")] + pub avg_maneuver_freq: Option, + #[serde(rename = "MAX_THRUST")] + pub max_thrust: Option, + #[serde(rename = "DV_BOL")] + pub dv_bol: Option, + #[serde(rename = "DV_REMAINING")] + pub dv_remaining: Option, + #[serde(rename = "IXX")] + pub ixx: Option, + #[serde(rename = "IYY")] + pub iyy: Option, + #[serde(rename = "IZZ")] + pub izz: Option, + #[serde(rename = "IXY")] + pub ixy: Option, + #[serde(rename = "IXZ")] + pub ixz: Option, + #[serde(rename = "IYZ")] + pub iyz: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct OcmCovarianceMatrixType { + #[serde(rename = "COMMENT")] + pub comment_list: Vec, + #[serde(rename = "COV_ID")] + pub cov_id: Option, + #[serde(rename = "COV_PREV_ID")] + pub cov_prev_id: Option, + #[serde(rename = "COV_NEXT_ID")] + pub cov_next_id: Option, + #[serde(rename = "COV_BASIS")] + pub cov_basis: Option, + #[serde(rename = "COV_BASIS_ID")] + pub cov_basis_id: Option, + #[serde(rename = "COV_REF_FRAME")] + pub cov_ref_frame: String, + #[serde(rename = "COV_FRAME_EPOCH")] + pub cov_frame_epoch: Option, + #[serde(rename = "COV_SCALE_MIN")] + pub cov_scale_min: Option, + #[serde(rename = "COV_SCALE_MAX")] + pub cov_scale_max: Option, + #[serde(rename = "COV_CONFIDENCE")] + pub cov_confidence: Option, + #[serde(rename = "COV_TYPE")] + pub cov_type: String, + #[serde(rename = "COV_ORDERING")] + pub cov_ordering: common::CovOrderType, + #[serde(rename = "COV_UNITS")] + pub cov_units: Option, + #[serde(rename = "covLine")] + pub cov_line_list: Vec, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct OcmManeuverParametersType { + #[serde(rename = "COMMENT")] + pub comment_list: Vec, + #[serde(rename = "MAN_ID")] + pub man_id: String, + #[serde(rename = "MAN_PREV_ID")] + pub man_prev_id: Option, + #[serde(rename = "MAN_NEXT_ID")] + pub man_next_id: Option, + #[serde(rename = "MAN_BASIS")] + pub man_basis: Option, + #[serde(rename = "MAN_BASIS_ID")] + pub man_basis_id: Option, + #[serde(rename = "MAN_DEVICE_ID")] + pub man_device_id: String, + #[serde(rename = "MAN_PREV_EPOCH")] + pub man_prev_epoch: Option, + #[serde(rename = "MAN_NEXT_EPOCH")] + pub man_next_epoch: Option, + #[serde(rename = "MAN_PURPOSE")] + pub man_purpose: Option, + #[serde(rename = "MAN_PRED_SOURCE")] + pub man_pred_source: Option, + #[serde(rename = "MAN_REF_FRAME")] + pub man_ref_frame: String, + #[serde(rename = "MAN_FRAME_EPOCH")] + pub man_frame_epoch: Option, + #[serde(rename = "GRAV_ASSIST_NAME")] + pub grav_assist_name: Option, + #[serde(rename = "DC_TYPE")] + pub dc_type: common::ManDcType, + #[serde(rename = "DC_WIN_OPEN")] + pub dc_win_open: Option, + #[serde(rename = "DC_WIN_CLOSE")] + pub dc_win_close: Option, + #[serde(rename = "DC_MIN_CYCLES")] + pub dc_min_cycles: Option, + #[serde(rename = "DC_MAX_CYCLES")] + pub dc_max_cycles: Option, + #[serde(rename = "DC_EXEC_START")] + pub dc_exec_start: Option, + #[serde(rename = "DC_EXEC_STOP")] + pub dc_exec_stop: Option, + #[serde(rename = "DC_REF_TIME")] + pub dc_ref_time: Option, + #[serde(rename = "DC_TIME_PULSE_DURATION")] + pub dc_time_pulse_duration: Option, + #[serde(rename = "DC_TIME_PULSE_PERIOD")] + pub dc_time_pulse_period: Option, + #[serde(rename = "DC_REF_DIR")] + pub dc_ref_dir: Option, + #[serde(rename = "DC_BODY_FRAME")] + pub dc_body_frame: Option, + #[serde(rename = "DC_BODY_TRIGGER")] + pub dc_body_trigger: Option, + #[serde(rename = "DC_PA_START_ANGLE")] + pub dc_pa_start_angle: Option, + #[serde(rename = "DC_PA_STOP_ANGLE")] + pub dc_pa_stop_angle: Option, + #[serde(rename = "MAN_COMPOSITION")] + pub man_composition: String, + #[serde(rename = "MAN_UNITS")] + pub man_units: Option, + #[serde(rename = "manLine")] + pub man_line_list: Vec, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct OcmPerturbationsType { + #[serde(rename = "COMMENT")] + pub comment_list: Vec, + #[serde(rename = "ATMOSPHERIC_MODEL")] + pub atmospheric_model: Option, + #[serde(rename = "GRAVITY_MODEL")] + pub gravity_model: Option, + #[serde(rename = "EQUATORIAL_RADIUS")] + pub equatorial_radius: Option, + #[serde(rename = "GM")] + pub gm: Option, + #[serde(rename = "N_BODY_PERTURBATIONS")] + pub n_body_perturbations: Option, + #[serde(rename = "CENTRAL_BODY_ROTATION")] + pub central_body_rotation: Option, + #[serde(rename = "OBLATE_FLATTENING")] + pub oblate_flattening: Option, + #[serde(rename = "OCEAN_TIDES_MODEL")] + pub ocean_tides_model: Option, + #[serde(rename = "SOLID_TIDES_MODEL")] + pub solid_tides_model: Option, + #[serde(rename = "REDUCTION_THEORY")] + pub reduction_theory: Option, + #[serde(rename = "ALBEDO_MODEL")] + pub albedo_model: Option, + #[serde(rename = "ALBEDO_GRID_SIZE")] + pub albedo_grid_size: Option, + #[serde(rename = "SHADOW_MODEL")] + pub shadow_model: Option, + #[serde(rename = "SHADOW_BODIES")] + pub shadow_bodies: Option, + #[serde(rename = "SRP_MODEL")] + pub srp_model: Option, + #[serde(rename = "SW_DATA_SOURCE")] + pub sw_data_source: Option, + #[serde(rename = "SW_DATA_EPOCH")] + pub sw_data_epoch: Option, + #[serde(rename = "SW_INTERP_METHOD")] + pub sw_interp_method: Option, + #[serde(rename = "FIXED_GEOMAG_KP")] + pub fixed_geomag_kp: Option, + #[serde(rename = "FIXED_GEOMAG_AP")] + pub fixed_geomag_ap: Option, + #[serde(rename = "FIXED_GEOMAG_DST")] + pub fixed_geomag_dst: Option, + #[serde(rename = "FIXED_F10P7")] + pub fixed_f10p7: Option, + #[serde(rename = "FIXED_F10P7_MEAN")] + pub fixed_f10p7_mean: Option, + #[serde(rename = "FIXED_M10P7")] + pub fixed_m10p7: Option, + #[serde(rename = "FIXED_M10P7_MEAN")] + pub fixed_m10p7_mean: Option, + #[serde(rename = "FIXED_S10P7")] + pub fixed_s10p7: Option, + #[serde(rename = "FIXED_S10P7_MEAN")] + pub fixed_s10p7_mean: Option, + #[serde(rename = "FIXED_Y10P7")] + pub fixed_y10p7: Option, + #[serde(rename = "FIXED_Y10P7_MEAN")] + pub fixed_y10p7_mean: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct OcmOdParametersType { + #[serde(rename = "COMMENT")] + pub comment_list: Vec, + #[serde(rename = "OD_ID")] + pub od_id: String, + #[serde(rename = "OD_PREV_ID")] + pub od_prev_id: Option, + #[serde(rename = "OD_METHOD")] + pub od_method: String, + #[serde(rename = "OD_EPOCH")] + pub od_epoch: common::EpochType, + #[serde(rename = "DAYS_SINCE_FIRST_OBS")] + pub days_since_first_obs: Option, + #[serde(rename = "DAYS_SINCE_LAST_OBS")] + pub days_since_last_obs: Option, + #[serde(rename = "RECOMMENDED_OD_SPAN")] + pub recommended_od_span: Option, + #[serde(rename = "ACTUAL_OD_SPAN")] + pub actual_od_span: Option, + #[serde(rename = "OBS_AVAILABLE")] + pub obs_available: Option, + #[serde(rename = "OBS_USED")] + pub obs_used: Option, + #[serde(rename = "TRACKS_AVAILABLE")] + pub tracks_available: Option, + #[serde(rename = "TRACKS_USED")] + pub tracks_used: Option, + #[serde(rename = "MAXIMUM_OBS_GAP")] + pub maximum_obs_gap: Option, + #[serde(rename = "OD_EPOCH_EIGMAJ")] + pub od_epoch_eigmaj: Option, + #[serde(rename = "OD_EPOCH_EIGINT")] + pub od_epoch_eigint: Option, + #[serde(rename = "OD_EPOCH_EIGMIN")] + pub od_epoch_eigmin: Option, + #[serde(rename = "OD_MAX_PRED_EIGMAJ")] + pub od_max_pred_eigmaj: Option, + #[serde(rename = "OD_MIN_PRED_EIGMIN")] + pub od_min_pred_eigmin: Option, + #[serde(rename = "OD_CONFIDENCE")] + pub od_confidence: Option, + #[serde(rename = "GDOP")] + pub gdop: Option, + #[serde(rename = "SOLVE_N")] + pub solve_n: Option, + #[serde(rename = "SOLVE_STATES")] + pub solve_states: Option, + #[serde(rename = "CONSIDER_N")] + pub consider_n: Option, + #[serde(rename = "CONSIDER_PARAMS")] + pub consider_params: Option, + #[serde(rename = "SEDR")] + pub sedr: Option, + #[serde(rename = "SENSORS_N")] + pub sensors_n: Option, + #[serde(rename = "SENSORS")] + pub sensors: Option, + #[serde(rename = "WEIGHTED_RMS")] + pub weighted_rms: Option, + #[serde(rename = "DATA_TYPES")] + pub data_types: Option, +} + +#[cfg(test)] +mod test { + use crate::ndm::xml::FromXmlStr; + + use super::*; + + #[test] + fn test_parse_ocm_message() { + let xml = r#" + +
+ ODM V.3 Example G-2 + OCM example with space object characteristics and perturbations. + This OCM reflects the latest conditions post-maneuver A67Z + This example shows the specification of multiple comment lines + 1998-11-06T09:23:57 + JAXA + OCM 201113719185 +
+ + + + 1998-999A + R. Rabbit + Flight Dynamics Mission Design Lead + (719)555-1234 + Mr. Rodgers + (719)555-1234 + email@email.XXX + UT1 + 1998-12-18T00:00:00.0000 + 36 + .357 + + + + GEOCENTRIC, CARTESIAN, EARTH FIXED + THIS IS MY SECOND COMMENT LINE + PREDICTED + EFG + CARTPVA + 0.0 2854.5 -2916.2 -5360.7 5.90 4.86 0.52 0.0037 -0.0038 -0.0070 + + + Spacecraft Physical Characteristics + 100.0 + 0.03123 + 0.78543 + 0.39158 + 0.47832 + 2.0 + 1.0 + 0.5 + 0.15 + 0.3 + 0.5 + + + Perturbations Specification + NRLMSIS00 + EGM-96: 36D 36O + 398600.4415 + MOON, SUN + 12.0 + 105.0 + 120.0 + + + WGS-84 + + + + +
"#; + + let message = OcmType::from_xml_str(xml).unwrap(); + + assert_eq!(message, OcmType { + header: common::OdmHeader { + comment_list: vec![ + "ODM V.3 Example G-2".to_string(), + "OCM example with space object characteristics and perturbations.".to_string(), + "This OCM reflects the latest conditions post-maneuver A67Z".to_string(), + "This example shows the specification of multiple comment lines".to_string(), + ], + classification_list: vec![], + creation_date: common::EpochType( + "1998-11-06T09:23:57".to_string(), + ), + originator: "JAXA".to_string(), + message_id: Some( + "OCM 201113719185".to_string(), + ), + }, + body: OcmBody { + segment: OcmSegment { + metadata: OcmMetadata { + comment_list: vec![], + object_name: None, + international_designator: Some( + "1998-999A".to_string(), + ), + catalog_name: None, + object_designator: None, + alternate_names: None, + originator_poc: Some( + "R. Rabbit".to_string(), + ), + originator_position: Some( + "Flight Dynamics Mission Design Lead".to_string(), + ), + originator_phone: Some( + "(719)555-1234".to_string(), + ), + originator_email: None, + originator_address: None, + tech_org: None, + tech_poc: Some( + "Mr. Rodgers".to_string(), + ), + tech_position: None, + tech_phone: Some( + "(719)555-1234".to_string(), + ), + tech_email: None, + tech_address: Some( + "email@email.XXX".to_string(), + ), + previous_message_id: None, + next_message_id: None, + adm_msg_link: None, + cdm_msg_link: None, + prm_msg_link: None, + rdm_msg_link: None, + tdm_msg_link: None, + operator: None, + owner: None, + country: None, + constellation: None, + object_type: None, + time_system: "UT1".to_string(), + epoch_tzero: common::EpochType( + "1998-12-18T00:00:00.0000".to_string(), + ), + ops_status: None, + orbit_category: None, + ocm_data_elements: None, + sclk_offset_at_epoch: None, + sclk_sec_per_si_sec: None, + previous_message_epoch: None, + next_message_epoch: None, + start_time: None, + stop_time: None, + time_span: None, + taimutc_at_tzero: Some( + common::TimeOffsetType { + base: 36.0, + units: Some( + common::TimeUnits( + "s".to_string(), + ), + ), + }, + ), + next_leap_epoch: None, + next_leap_taimutc: None, + ut1mutc_at_tzero: Some( + common::TimeOffsetType { + base: 0.357, + units: Some( + common::TimeUnits( + "s".to_string(), + ), + ), + }, + ), + eop_source: None, + interp_method_eop: None, + celestial_source: None, + }, + data: OcmData { + traj_list: vec![ + OcmTrajStateType { + comment_list: vec![ + "GEOCENTRIC, CARTESIAN, EARTH FIXED".to_string(), + "THIS IS MY SECOND COMMENT LINE".to_string(), + ], + traj_id: None, + traj_prev_id: None, + traj_next_id: None, + traj_basis: Some( + common::TrajBasisType( + "PREDICTED".to_string(), + ), + ), + traj_basis_id: None, + interpolation: None, + interpolation_degree: None, + propagator: None, + center_name: "".to_string(), + traj_ref_frame: "EFG".to_string(), + traj_frame_epoch: None, + useable_start_time: None, + useable_stop_time: None, + orb_revnum: None, + orb_revnum_basis: None, + traj_type: "CARTPVA".to_string(), + orb_averaging: None, + traj_units: None, + traj_line_list: vec![ + "0.0 2854.5 -2916.2 -5360.7 5.90 4.86 0.52 0.0037 -0.0038 -0.0070".to_string(), + ], + }, + ], + phys: Some( + OcmPhysicalDescriptionType { + comment_list: vec![ + "Spacecraft Physical Characteristics".to_string(), + ], + manufacturer: None, + bus_model: None, + docked_with: None, + drag_const_area: None, + drag_coeff_nom: None, + drag_uncertainty: None, + initial_wet_mass: None, + wet_mass: Some( + common::MassType { + base: common::NonNegativeDouble( + 100.0, + ), + units: Some( + common::MassUnits( + "kg".to_string(), + ), + ), + }, + ), + dry_mass: None, + oeb_parent_frame: None, + oeb_parent_frame_epoch: None, + oeb_q1: Some( + 0.03123, + ), + oeb_q2: Some( + 0.78543, + ), + oeb_q3: Some( + 0.39158, + ), + oeb_qc: Some( + 0.47832, + ), + oeb_max: Some( + common::OcmLengthType { + base: 2.0, + units: Some( + common::LengthUnits( + "m".to_string(), + ), + ), + }, + ), + oeb_int: Some( + common::OcmLengthType { + base: 1.0, + units: Some( + common::LengthUnits( + "m".to_string(), + ), + ), + }, + ), + oeb_min: Some( + common::OcmLengthType { + base: 0.5, + units: Some( + common::LengthUnits( + "m".to_string(), + ), + ), + }, + ), + area_along_oeb_max: Some( + common::AreaType { + base: common::NonNegativeDouble( + 0.15, + ), + units: Some( + common::AreaUnits( + "m**2".to_string(), + ), + ), + }, + ), + area_along_oeb_int: Some( + common::AreaType { + base: common::NonNegativeDouble( + 0.3, + ), + units: Some( + common::AreaUnits( + "m**2".to_string(), + ), + ), + }, + ), + area_along_oeb_min: Some( + common::AreaType { + base: common::NonNegativeDouble( + 0.5, + ), + units: Some( + common::AreaUnits( + "m**2".to_string(), + ), + ), + }, + ), + area_min_for_pc: None, + area_max_for_pc: None, + area_typ_for_pc: None, + rcs: None, + rcs_min: None, + rcs_max: None, + srp_const_area: None, + solar_rad_coeff: None, + solar_rad_uncertainty: None, + vm_absolute: None, + vm_apparent_min: None, + vm_apparent: None, + vm_apparent_max: None, + reflectance: None, + att_control_mode: None, + att_actuator_type: None, + att_knowledge: None, + att_control: None, + att_pointing: None, + avg_maneuver_freq: None, + max_thrust: None, + dv_bol: None, + dv_remaining: None, + ixx: None, + iyy: None, + izz: None, + ixy: None, + ixz: None, + iyz: None, + }, + ), + cov_list: vec![], + man_list: vec![], + pert: Some( + OcmPerturbationsType { + comment_list: vec![ + "Perturbations Specification".to_string(), + ], + atmospheric_model: Some( + "NRLMSIS00".to_string(), + ), + gravity_model: Some( + "EGM-96: 36D 36O".to_string(), + ), + equatorial_radius: None, + gm: Some( + common::GmType { + base: common::PositiveDouble( + 398600.4415, + ), + units: Some( + common::GmUnits( + "km**3/s**2".to_string(), + ), + ), + }, + ), + n_body_perturbations: Some( + "MOON, SUN".to_string(), + ), + central_body_rotation: None, + oblate_flattening: None, + ocean_tides_model: None, + solid_tides_model: None, + reduction_theory: None, + albedo_model: None, + albedo_grid_size: None, + shadow_model: None, + shadow_bodies: None, + srp_model: None, + sw_data_source: None, + sw_data_epoch: None, + sw_interp_method: None, + fixed_geomag_kp: Some( + common::GeomagType { + base: 12.0, + units: None, + }, + ), + fixed_geomag_ap: None, + fixed_geomag_dst: None, + fixed_f10p7: Some( + common::SolarFluxType { + base: 105.0, + units: None, + }, + ), + fixed_f10p7_mean: Some( + common::SolarFluxType { + base: 120.0, + units: None, + }, + ), + fixed_m10p7: None, + fixed_m10p7_mean: None, + fixed_s10p7: None, + fixed_s10p7_mean: None, + fixed_y10p7: None, + fixed_y10p7_mean: None, + }, + ), + od: None, + user: Some( + common::UserDefinedType { + comment_list: vec![], + user_defined_list: vec![ + common::UserDefinedParameterType { + base: "WGS-84".to_string(), + parameter: "EARTH_MODEL".to_string(), + }, + ], + }, + ), + }, + }, + }, + id: "CCSDS_OCM_VERS".to_string(), + version: "3.0".to_string(), + }); + } +} diff --git a/crates/lox-io/src/ndm/oem.rs b/crates/lox-io/src/ndm/oem.rs new file mode 100644 index 00000000..8f1c3eff --- /dev/null +++ b/crates/lox-io/src/ndm/oem.rs @@ -0,0 +1,756 @@ +/* + * Copyright (c) 2023. Helge Eichhorn and the LOX contributors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + */ + +//! Deserializers for XML and KVN CCSDS Orbit Ephemeris Message +//! +//! To deserialize an XML message: +//! +//! ``` +//! # let xml = r#" +//! # +//! # +//! #
+//! # 2004-281T17:26:06 +//! # me +//! #
+//! # +//! # +//! # +//! # Cassini +//! # 1997-061A +//! # Saturn +//! # IAU-Saturn +//! # UTC +//! # 2004-100T00:00:00.000000 +//! # 2004-100T01:00:00.000000 +//! # Hermite +//! # 1 +//! # +//! # +//! # +//! # 2004-100T00:00:00 +//! # 1 +//! # 1 +//! # 1 +//! # 1 +//! # 1 +//! # 1 +//! # +//! # +//! # 2004-100T00:00:00 +//! # 1 +//! # 1 +//! # 1 +//! # 1 +//! # 1 +//! # 1 +//! # +//! # +//! # 2004-100T00:00:00 +//! # 1 +//! # 1 +//! # 1 +//! # 1 +//! # 1 +//! # 1 +//! # +//! # +//! # +//! # +//! #
"#; +//! # +//! # use lox_io::ndm::oem::OemType; +//! use lox_io::ndm::xml::FromXmlStr; +//! +//! let message = OemType::from_xml_str(xml).unwrap(); +//! ``` + +// This file is partially generated with xml-schema-derive from the XSD schema +// published by CCSDS. Adaptations have been made to simplify the types or +// allow to simplify the implementation of the KVN parser. + +use serde; + +use super::common; + +#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct OemType { + #[serde(rename = "header")] + pub header: common::OdmHeader, + #[serde(rename = "body")] + pub body: OemBody, + #[serde(rename = "@id")] + pub id: String, + #[serde(rename = "@version")] + pub version: String, +} + +impl crate::ndm::xml::FromXmlStr<'_> for OemType {} + +#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct OemBody { + #[serde(rename = "segment")] + pub segment_list: Vec, +} + +#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct OemSegment { + #[serde(rename = "metadata")] + pub metadata: OemMetadata, + #[serde(rename = "data")] + pub data: OemData, +} + +#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct OemMetadata { + #[serde(rename = "COMMENT")] + pub comment_list: Vec, + #[serde(rename = "OBJECT_NAME")] + pub object_name: String, + #[serde(rename = "OBJECT_ID")] + pub object_id: String, + #[serde(rename = "CENTER_NAME")] + pub center_name: String, + #[serde(rename = "REF_FRAME")] + pub ref_frame: String, + #[serde(rename = "REF_FRAME_EPOCH")] + pub ref_frame_epoch: Option, + #[serde(rename = "TIME_SYSTEM")] + pub time_system: String, + #[serde(rename = "START_TIME")] + pub start_time: common::EpochType, + #[serde(rename = "USEABLE_START_TIME")] + pub useable_start_time: Option, + #[serde(rename = "USEABLE_STOP_TIME")] + pub useable_stop_time: Option, + #[serde(rename = "STOP_TIME")] + pub stop_time: common::EpochType, + #[serde(rename = "INTERPOLATION")] + pub interpolation: Option, + #[serde(rename = "INTERPOLATION_DEGREE")] + pub interpolation_degree: Option, +} + +#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct OemData { + #[serde(rename = "COMMENT")] + pub comment_list: Vec, + #[serde(rename = "stateVector")] + pub state_vector_list: Vec, + #[serde(rename = "covarianceMatrix")] + pub covariance_matrix_list: Vec, +} + +#[cfg(test)] +mod test { + use crate::ndm::xml::FromXmlStr; + + use super::*; + + #[test] + fn test_parse_oem_message1() { + let xml = r#" + + +
+ 2004-281T17:26:06 + me +
+ + + + Cassini + 1997-061A + Saturn + IAU-Saturn + UTC + 2004-100T00:00:00.000000 + 2004-100T01:00:00.000000 + Hermite + 1 + + + + 2004-100T00:00:00 + 1 + 1 + 1 + 1 + 1 + 1 + + + 2004-100T00:00:00 + 1 + 1 + 1 + 1 + 1 + 1 + + + 2004-100T00:00:00 + 1 + 1 + 1 + 1 + 1 + 1 + + + + +
"#; + + let message = OemType::from_xml_str(xml).unwrap(); + + assert_eq!( + message, + OemType { + header: common::OdmHeader { + comment_list: vec![], + classification_list: vec![], + creation_date: common::EpochType("2004-281T17:26:06".to_string()), + originator: "me".to_string(), + message_id: None, + }, + body: OemBody { + segment_list: vec![OemSegment { + metadata: OemMetadata { + comment_list: vec![], + object_name: "Cassini".to_string(), + object_id: "1997-061A".to_string(), + center_name: "Saturn".to_string(), + ref_frame: "IAU-Saturn".to_string(), + ref_frame_epoch: None, + time_system: "UTC".to_string(), + start_time: common::EpochType("2004-100T00:00:00.000000".to_string()), + useable_start_time: None, + useable_stop_time: None, + stop_time: common::EpochType("2004-100T01:00:00.000000".to_string()), + interpolation: Some("Hermite".to_string()), + interpolation_degree: Some(1,), + }, + data: OemData { + comment_list: vec![], + state_vector_list: vec![ + common::StateVectorAccType { + epoch: common::EpochType("2004-100T00:00:00".to_string()), + x: common::PositionType { + base: 1.0, + units: Some(common::PositionUnits("km".to_string()),), + }, + y: common::PositionType { + base: 1.0, + units: None, + }, + z: common::PositionType { + base: 1.0, + units: None, + }, + x_dot: common::VelocityType { + base: 1.0, + units: Some(common::VelocityUnits("km/s".to_string()),), + }, + y_dot: common::VelocityType { + base: 1.0, + units: None, + }, + z_dot: common::VelocityType { + base: 1.0, + units: None, + }, + x_ddot: None, + y_ddot: None, + z_ddot: None, + }, + common::StateVectorAccType { + epoch: common::EpochType("2004-100T00:00:00".to_string()), + x: common::PositionType { + base: 1.0, + units: None, + }, + y: common::PositionType { + base: 1.0, + units: Some(common::PositionUnits("km".to_string()),), + }, + z: common::PositionType { + base: 1.0, + units: None, + }, + x_dot: common::VelocityType { + base: 1.0, + units: None, + }, + y_dot: common::VelocityType { + base: 1.0, + units: Some(common::VelocityUnits("km/s".to_string()),), + }, + z_dot: common::VelocityType { + base: 1.0, + units: None, + }, + x_ddot: None, + y_ddot: None, + z_ddot: None, + }, + common::StateVectorAccType { + epoch: common::EpochType("2004-100T00:00:00".to_string()), + x: common::PositionType { + base: 1.0, + units: None, + }, + y: common::PositionType { + base: 1.0, + units: None, + }, + z: common::PositionType { + base: 1.0, + units: Some(common::PositionUnits("km".to_string()),), + }, + x_dot: common::VelocityType { + base: 1.0, + units: None, + }, + y_dot: common::VelocityType { + base: 1.0, + units: None, + }, + z_dot: common::VelocityType { + base: 1.0, + units: Some(common::VelocityUnits("km/s".to_string()),), + }, + x_ddot: None, + y_ddot: None, + z_ddot: None, + }, + ], + covariance_matrix_list: vec![], + }, + },], + }, + id: "CCSDS_OEM_VERS".to_string(), + version: "2.0".to_string(), + } + ); + } + + #[test] + fn test_parse_oem_message2() { + let xml = r#" + + +
+ OEM WITH OPTIONAL ACCELERATIONS + 1996-11-04T17:22:31 + NASA/JPL + OEM 201113719185 +
+ + + + MARS GLOBAL SURVEYOR + 2000-028A + MARS BARYCENTER + J2000 + UTC + 1996-12-18T12:00:00.331 + 1996-12-18T12:10:00.331 + 1996-12-28T21:23:00.331 + 1996-12-28T21:28:00.331 + HERMITE + 7 + + + Produced by M.R. Sombedody, MSOO NAV/JPL, 1996 OCT 11. It is + to be used for DSN scheduling purposes only. + + 1996-12-18T12:00:00.331 + 2789.6 + -280.0 + -1746.8 + 4.73 + -2.50 + -1.04 + 0.008 + 0.001 + -0.159 + + + 1996-12-18T12:01:00.331 + 2783.4 + -308.1 + -1877.1 + 5.19 + -2.42 + -2.00 + 0.008 + 0.001 + 0.001 + + + 1996-12-18T12:02:00.331 + 2776.0 + -336.9 + -2008.7 + 5.64 + -2.34 + -1.95 + 0.008 + 0.001 + 0.159 + + + 1996-12-28T21:28:00.331 + -3881.0 + 564.0 + -682.8 + -3.29 + -3.67 + 1.64 + -0.003 + 0.000 + 0.000 + + + blabla + 1996-12-28T22:28:00.331 + ITRF1997 + 0.316 + 0.722 + 0.518 + 0.202 + 0.715 + 0.002 + 0.912 + 0.306 + 0.276 + 0.797 + 0.562 + 0.899 + 0.022 + 0.079 + 0.415 + 0.245 + 0.965 + 0.950 + 0.435 + 0.621 + 0.991 + + + + +
"#; + + let message = OemType::from_xml_str(xml).unwrap(); + + assert_eq!( + message, + OemType { + header: common::OdmHeader { + comment_list: vec!["OEM WITH OPTIONAL ACCELERATIONS".to_string()], + classification_list: vec![], + creation_date: common::EpochType("1996-11-04T17:22:31".to_string()), + originator: "NASA/JPL".to_string(), + message_id: Some("OEM 201113719185".to_string()), + }, + body: OemBody { + segment_list: vec![OemSegment { + metadata: OemMetadata { + comment_list: vec![], + object_name: "MARS GLOBAL SURVEYOR".to_string(), + object_id: "2000-028A".to_string(), + center_name: "MARS BARYCENTER".to_string(), + ref_frame: "J2000".to_string(), + ref_frame_epoch: None, + time_system: "UTC".to_string(), + start_time: common::EpochType("1996-12-18T12:00:00.331".to_string()), + useable_start_time: Some(common::EpochType( + "1996-12-18T12:10:00.331".to_string(), + )), + useable_stop_time: Some(common::EpochType( + "1996-12-28T21:23:00.331".to_string(), + )), + stop_time: common::EpochType("1996-12-28T21:28:00.331".to_string()), + interpolation: Some("HERMITE".to_string()), + interpolation_degree: Some(7,), + }, + data: OemData { + comment_list: vec![ + "Produced by M.R. Sombedody, MSOO NAV/JPL, 1996 OCT 11. It is" + .to_string(), + "to be used for DSN scheduling purposes only.".to_string(), + ], + state_vector_list: vec![ + common::StateVectorAccType { + epoch: common::EpochType("1996-12-18T12:00:00.331".to_string()), + x: common::PositionType { + base: 2789.6, + units: None, + }, + y: common::PositionType { + base: -280.0, + units: None, + }, + z: common::PositionType { + base: -1746.8, + units: None, + }, + x_dot: common::VelocityType { + base: 4.73, + units: None, + }, + y_dot: common::VelocityType { + base: -2.5, + units: None, + }, + z_dot: common::VelocityType { + base: -1.04, + units: None, + }, + x_ddot: Some(common::AccType { + base: 0.008, + units: None, + },), + y_ddot: Some(common::AccType { + base: 0.001, + units: None, + },), + z_ddot: Some(common::AccType { + base: -0.159, + units: None, + },), + }, + common::StateVectorAccType { + epoch: common::EpochType("1996-12-18T12:01:00.331".to_string()), + x: common::PositionType { + base: 2783.4, + units: None, + }, + y: common::PositionType { + base: -308.1, + units: None, + }, + z: common::PositionType { + base: -1877.1, + units: None, + }, + x_dot: common::VelocityType { + base: 5.19, + units: None, + }, + y_dot: common::VelocityType { + base: -2.42, + units: None, + }, + z_dot: common::VelocityType { + base: -2.0, + units: None, + }, + x_ddot: Some(common::AccType { + base: 0.008, + units: None, + },), + y_ddot: Some(common::AccType { + base: 0.001, + units: None, + },), + z_ddot: Some(common::AccType { + base: 0.001, + units: None, + },), + }, + common::StateVectorAccType { + epoch: common::EpochType("1996-12-18T12:02:00.331".to_string()), + x: common::PositionType { + base: 2776.0, + units: None, + }, + y: common::PositionType { + base: -336.9, + units: None, + }, + z: common::PositionType { + base: -2008.7, + units: None, + }, + x_dot: common::VelocityType { + base: 5.64, + units: None, + }, + y_dot: common::VelocityType { + base: -2.34, + units: None, + }, + z_dot: common::VelocityType { + base: -1.95, + units: None, + }, + x_ddot: Some(common::AccType { + base: 0.008, + units: None, + },), + y_ddot: Some(common::AccType { + base: 0.001, + units: None, + },), + z_ddot: Some(common::AccType { + base: 0.159, + units: None, + },), + }, + common::StateVectorAccType { + epoch: common::EpochType("1996-12-28T21:28:00.331".to_string()), + x: common::PositionType { + base: -3881.0, + units: None, + }, + y: common::PositionType { + base: 564.0, + units: None, + }, + z: common::PositionType { + base: -682.8, + units: None, + }, + x_dot: common::VelocityType { + base: -3.29, + units: None, + }, + y_dot: common::VelocityType { + base: -3.67, + units: None, + }, + z_dot: common::VelocityType { + base: 1.64, + units: None, + }, + x_ddot: Some(common::AccType { + base: -0.003, + units: None, + },), + y_ddot: Some(common::AccType { + base: 0.0, + units: None, + },), + z_ddot: Some(common::AccType { + base: 0.0, + units: None, + },), + }, + ], + covariance_matrix_list: vec![common::OemCovarianceMatrixType { + comment_list: vec!["blabla".to_string()], + epoch: common::EpochType("1996-12-28T22:28:00.331".to_string()), + cov_ref_frame: Some("ITRF1997".to_string()), + cx_x: common::PositionCovarianceType { + base: 0.316, + units: None, + }, + cy_x: common::PositionCovarianceType { + base: 0.722, + units: None, + }, + cy_y: common::PositionCovarianceType { + base: 0.518, + units: None, + }, + cz_x: common::PositionCovarianceType { + base: 0.202, + units: None, + }, + cz_y: common::PositionCovarianceType { + base: 0.715, + units: None, + }, + cz_z: common::PositionCovarianceType { + base: 0.002, + units: None, + }, + cx_dot_x: common::PositionVelocityCovarianceType { + base: 0.912, + units: None, + }, + cx_dot_y: common::PositionVelocityCovarianceType { + base: 0.306, + units: None, + }, + cx_dot_z: common::PositionVelocityCovarianceType { + base: 0.276, + units: None, + }, + cx_dot_x_dot: common::VelocityCovarianceType { + base: 0.797, + units: None, + }, + cy_dot_x: common::PositionVelocityCovarianceType { + base: 0.562, + units: None, + }, + cy_dot_y: common::PositionVelocityCovarianceType { + base: 0.899, + units: None, + }, + cy_dot_z: common::PositionVelocityCovarianceType { + base: 0.022, + units: None, + }, + cy_dot_x_dot: common::VelocityCovarianceType { + base: 0.079, + units: None, + }, + cy_dot_y_dot: common::VelocityCovarianceType { + base: 0.415, + units: None, + }, + cz_dot_x: common::PositionVelocityCovarianceType { + base: 0.245, + units: None, + }, + cz_dot_y: common::PositionVelocityCovarianceType { + base: 0.965, + units: None, + }, + cz_dot_z: common::PositionVelocityCovarianceType { + base: 0.95, + units: None, + }, + cz_dot_x_dot: common::VelocityCovarianceType { + base: 0.435, + units: None, + }, + cz_dot_y_dot: common::VelocityCovarianceType { + base: 0.621, + units: None, + }, + cz_dot_z_dot: common::VelocityCovarianceType { + base: 0.991, + units: None, + }, + },], + }, + },], + }, + id: "CCSDS_OEM_VERS".to_string(), + version: "3.0".to_string(), + } + ); + } +} diff --git a/crates/lox-io/src/ndm/omm.rs b/crates/lox-io/src/ndm/omm.rs new file mode 100644 index 00000000..6e6f588d --- /dev/null +++ b/crates/lox-io/src/ndm/omm.rs @@ -0,0 +1,1780 @@ +/* + * Copyright (c) 2023. Helge Eichhorn and the LOX contributors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + */ + +//! Deserializers for XML and KVN CCSDS Orbit Mean Elements Message +//! +//! To deserialize an XML message: +//! +//! ``` +//! # let xml = r#" +//! # +//! #
+//! # 2021-03-24T23:00:00.000 +//! # CelesTrak +//! #
+//! # +//! # +//! # +//! # STARLETTE +//! # 1975-010A +//! # EARTH +//! # TEME +//! # UTC +//! # SGP4 +//! # +//! # +//! # +//! # 2008-09-20T12:25:40.104192 +//! # 15.72125391 +//! # 0.0006703 +//! # 51.6416 +//! # 247.4627 +//! # 130.5360 +//! # 325.0288 +//! # 398600.8 +//! # +//! # +//! # 0 +//! # U +//! # 7646 +//! # 999 +//! # 32997 +//! # -.47102E-5 +//! # -.147E-5 +//! # 0 +//! # +//! # +//! # foo enters +//! # a bar +//! # +//! # +//! # +//! # +//! #
"#; +//! # +//! # use lox_io::ndm::omm::OmmType; +//! use lox_io::ndm::xml::FromXmlStr; +//! +//! let message = OmmType::from_xml_str(xml).unwrap(); +//! ``` +//! +//! To deserialize a KVN message: +//! ``` +//! # let kvn = r#"CCSDS_OMM_VERS = 3.0 +//! # COMMENT this is a comment +//! # COMMENT here is another one +//! # CREATION_DATE = 2007-06-05T16:00:00 +//! # ORIGINATOR = NOAA/USA +//! # COMMENT this comment doesn't say much +//! # OBJECT_NAME = GOES 9 +//! # OBJECT_ID = 1995-025A +//! # CENTER_NAME = EARTH +//! # REF_FRAME = TOD +//! # REF_FRAME_EPOCH = 2000-01-03T10:34:00 +//! # TIME_SYSTEM = MRT +//! # MEAN_ELEMENT_THEORY = SOME THEORY +//! # COMMENT the following data is what we're looking for +//! # EPOCH = 2000-01-05T10:00:00 +//! # SEMI_MAJOR_AXIS = 6800 +//! # ECCENTRICITY = 0.0005013 +//! # INCLINATION = 3.0539 +//! # RA_OF_ASC_NODE = 81.7939 +//! # ARG_OF_PERICENTER = 249.2363 +//! # MEAN_ANOMALY = 150.1602 +//! # COMMENT spacecraft data +//! # MASS = 300 +//! # SOLAR_RAD_AREA = 5 +//! # SOLAR_RAD_COEFF = 0.001 +//! # DRAG_AREA = 4 +//! # DRAG_COEFF = 0.002 +//! # COMMENT Covariance matrix +//! # COV_REF_FRAME = TNW +//! # CX_X = 3.331349476038534e-04 +//! # CY_X = 4.618927349220216e-04 +//! # CY_Y = 6.782421679971363e-04 +//! # CZ_X = -3.070007847730449e-04 +//! # CZ_Y = -4.221234189514228e-04 +//! # CZ_Z = 3.231931992380369e-04 +//! # CX_DOT_X = -3.349365033922630e-07 +//! # CX_DOT_Y = -4.686084221046758e-07 +//! # CX_DOT_Z = 2.484949578400095e-07 +//! # CX_DOT_X_DOT = 4.296022805587290e-10 +//! # CY_DOT_X = -2.211832501084875e-07 +//! # CY_DOT_Y = -2.864186892102733e-07 +//! # CY_DOT_Z = 1.798098699846038e-07 +//! # CY_DOT_X_DOT = 2.608899201686016e-10 +//! # CY_DOT_Y_DOT = 1.767514756338532e-10 +//! # CZ_DOT_X = -3.041346050686871e-07 +//! # CZ_DOT_Y = -4.989496988610662e-07 +//! # CZ_DOT_Z = 3.540310904497689e-07 +//! # CZ_DOT_X_DOT = 1.869263192954590e-10 +//! # CZ_DOT_Y_DOT = 1.008862586240695e-10 +//! # CZ_DOT_Z_DOT = 6.224444338635500e-10"#; +//! # +//! # use lox_io::ndm::omm::OmmType; +//! use lox_io::ndm::kvn::KvnDeserializer; +//! +//! let message: OmmType = KvnDeserializer::from_kvn_str(&kvn).unwrap(); +//! ``` + +// This file is partially generated with xml-schema-derive from the XSD schema +// published by CCSDS. Adaptations have been made to simplify the types or +// allow to simplify the implementation of the KVN parser. + +use serde; + +use super::common; + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct BStarUnits(#[serde(rename = "$text")] pub String); + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct BTermUnits(#[serde(rename = "$text")] pub String); + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct AgomUnits(#[serde(rename = "$text")] pub String); + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct ElementSetNoType(#[serde(rename = "$text")] pub String); + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct RevUnits(#[serde(rename = "$text")] pub String); + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct DRevUnits(#[serde(rename = "$text")] pub String); + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct DdRevUnits(#[serde(rename = "$text")] pub String); + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct SpacewarnType(#[serde(rename = "$text")] pub String); + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct OmmType { + #[serde(rename = "@id")] + // Marked as option for the KVN deserializer + pub id: Option, + #[serde(rename = "@version")] + pub version: String, + #[serde(rename = "header")] + pub header: common::OdmHeader, + #[serde(rename = "body")] + pub body: OmmBody, +} + +impl crate::ndm::xml::FromXmlStr<'_> for OmmType {} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct OmmBody { + #[serde(rename = "segment")] + pub segment: OmmSegment, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct OmmSegment { + #[serde(rename = "metadata")] + pub metadata: OmmMetadata, + #[serde(rename = "data")] + pub data: OmmData, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct OmmMetadata { + #[serde(rename = "COMMENT")] + pub comment_list: Vec, + #[serde(rename = "OBJECT_NAME")] + pub object_name: String, + #[serde(rename = "OBJECT_ID")] + pub object_id: String, + #[serde(rename = "CENTER_NAME")] + pub center_name: String, + #[serde(rename = "REF_FRAME")] + pub ref_frame: String, + #[serde(rename = "REF_FRAME_EPOCH")] + pub ref_frame_epoch: Option, + #[serde(rename = "TIME_SYSTEM")] + pub time_system: String, + #[serde(rename = "MEAN_ELEMENT_THEORY")] + pub mean_element_theory: String, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct OmmData { + #[serde(rename = "COMMENT")] + pub comment_list: Vec, + #[serde(rename = "meanElements")] + pub mean_elements: MeanElementsType, + #[serde(rename = "spacecraftParameters")] + pub spacecraft_parameters: Option, + #[serde(rename = "tleParameters")] + pub tle_parameters: Option, + #[serde(rename = "covarianceMatrix")] + pub covariance_matrix: Option, + #[serde(rename = "userDefinedParameters")] + pub user_defined_parameters: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct MeanElementsType { + #[serde(rename = "COMMENT")] + pub comment_list: Vec, + #[serde(rename = "EPOCH")] + pub epoch: common::EpochType, + #[serde(rename = "SEMI_MAJOR_AXIS")] + pub semi_major_axis: Option, + #[serde(rename = "MEAN_MOTION")] + pub mean_motion: Option, + #[serde(rename = "ECCENTRICITY")] + pub eccentricity: common::NonNegativeDouble, + #[serde(rename = "INCLINATION")] + pub inclination: common::InclinationType, + #[serde(rename = "RA_OF_ASC_NODE")] + pub ra_of_asc_node: common::AngleType, + #[serde(rename = "ARG_OF_PERICENTER")] + pub arg_of_pericenter: common::AngleType, + #[serde(rename = "MEAN_ANOMALY")] + pub mean_anomaly: common::AngleType, + #[serde(rename = "GM")] + pub gm: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct TleParametersType { + #[serde(rename = "COMMENT")] + pub comment_list: Vec, + #[serde(rename = "EPHEMERIS_TYPE")] + pub ephemeris_type: Option, + #[serde(rename = "CLASSIFICATION_TYPE")] + pub classification_type: Option, + #[serde(rename = "NORAD_CAT_ID")] + pub norad_cat_id: Option, + #[serde(rename = "ELEMENT_SET_NO")] + pub element_set_no: Option, + #[serde(rename = "REV_AT_EPOCH")] + pub rev_at_epoch: Option, + #[serde(rename = "BSTAR")] + pub bstar: Option, + #[serde(rename = "BTERM")] + pub bterm: Option, + #[serde(rename = "MEAN_MOTION_DOT")] + pub mean_motion_dot: DRevType, + #[serde(rename = "MEAN_MOTION_DDOT")] + pub mean_motion_ddot: Option, + #[serde(rename = "AGOM")] + pub agom: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct BStarType { + #[serde(rename = "$text")] + pub base: f64, + #[serde(rename = "@units")] + pub units: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct BTermType { + #[serde(rename = "$text")] + pub base: f64, + #[serde(rename = "@units")] + pub units: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct AgomType { + #[serde(rename = "$text")] + pub base: f64, + #[serde(rename = "@units")] + pub units: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct RevType { + #[serde(rename = "$text")] + pub base: f64, + #[serde(rename = "@units")] + pub units: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct DRevType { + #[serde(rename = "$text")] + pub base: f64, + #[serde(rename = "@units")] + pub units: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct DdRevType { + #[serde(rename = "$text")] + pub base: f64, + #[serde(rename = "@units")] + pub units: Option, +} + +#[cfg(test)] +mod test { + use crate::ndm::xml::FromXmlStr; + + use super::*; + + #[test] + fn test_parse_omm_message_xml_1() { + let xml = r#" + + +
+ THIS EXAMPLE CONFORMS TO FIGURE 4-2 IN 502.0-B-2 + 2007-065T16:00:00 + NOAA/USA +
+ + + + GOES-9 + 1995-025A + EARTH + TEME + UTC + TLE + + + USAF SGP4 IS THE ONLY PROPAGATOR THAT SHOULD BE USED FOR THIS DATA + + 2007-064T10:34:41.4264 + 1.00273272 + 0.0005013 + 3.0539 + 81.7939 + 249.2363 + 150.1602 + 398600.8 + + + 23581 + 0925 + 4316 + 0.0001 + -0.00000113 + 0.0 + + + xyz + 9 + xyz + 9 + xyz + 9 + xyz + 9 + xyz + 9 + + + + +
"#; + + let message = OmmType::from_xml_str(xml).unwrap(); + + assert_eq!(message, + OmmType { + header: common::OdmHeader { + comment_list: vec![ + "THIS EXAMPLE CONFORMS TO FIGURE 4-2 IN 502.0-B-2".to_string(), + ], + classification_list: vec![], + creation_date: common::EpochType( + "2007-065T16:00:00".to_string(), + ), + originator: "NOAA/USA".to_string(), + message_id: None, + }, + body: OmmBody { + segment: OmmSegment { + metadata: OmmMetadata { + comment_list: vec![], + object_name: "GOES-9".to_string(), + object_id: "1995-025A".to_string(), + center_name: "EARTH".to_string(), + ref_frame: "TEME".to_string(), + ref_frame_epoch: None, + time_system: "UTC".to_string(), + mean_element_theory: "TLE".to_string(), + }, + data: OmmData { + comment_list: vec![ + "USAF SGP4 IS THE ONLY PROPAGATOR THAT SHOULD BE USED FOR THIS DATA".to_string(), + ], + mean_elements: MeanElementsType { + comment_list: vec![], + epoch: common::EpochType( + "2007-064T10:34:41.4264".to_string(), + ), + semi_major_axis: None, + mean_motion: Some( + RevType { + base: 1.00273272, + units: None, + }, + ), + eccentricity: common::NonNegativeDouble( + 0.0005013, + ), + inclination: common::InclinationType { + base: 3.0539, + units: None, + }, + ra_of_asc_node: common::AngleType { + base: 81.7939, + units: None, + }, + arg_of_pericenter: common::AngleType { + base: 249.2363, + units: None, + }, + mean_anomaly: common::AngleType { + base: 150.1602, + units: None, + }, + gm: Some( + common::GmType { + base: common::PositiveDouble( + 398600.8, + ), + units: None, + }, + ), + }, + spacecraft_parameters: None, + tle_parameters: Some( + TleParametersType { + comment_list: vec![], + ephemeris_type: None, + classification_type: None, + norad_cat_id: Some( + 23581, + ), + element_set_no: Some( + ElementSetNoType( + "0925".to_string(), + ), + ), + rev_at_epoch: Some( + 4316, + ), + bstar: Some( + BStarType { + base: 0.0001, + units: None, + }, + ), + bterm: None, + mean_motion_dot: DRevType { + base: -1.13e-6, + units: None, + }, + mean_motion_ddot: Some( + DRevType { + base: 0.0, + units: None, + }, + ), + agom: None, + }, + ), + covariance_matrix: None, + user_defined_parameters: Some( + common::UserDefinedType { + comment_list: vec![], + user_defined_list: vec![ + common::UserDefinedParameterType { + base: "xyz".to_string(), + parameter: "ABC0".to_string(), + }, + common::UserDefinedParameterType { + base: "9".to_string(), + parameter: "ABC1".to_string(), + }, + common::UserDefinedParameterType { + base: "xyz".to_string(), + parameter: "ABC2".to_string(), + }, + common::UserDefinedParameterType { + base: "9".to_string(), + parameter: "ABC3".to_string(), + }, + common::UserDefinedParameterType { + base: "xyz".to_string(), + parameter: "ABC4".to_string(), + }, + common::UserDefinedParameterType { + base: "9".to_string(), + parameter: "ABC5".to_string(), + }, + common::UserDefinedParameterType { + base: "xyz".to_string(), + parameter: "ABC6".to_string(), + }, + common::UserDefinedParameterType { + base: "9".to_string(), + parameter: "ABC7".to_string(), + }, + common::UserDefinedParameterType { + base: "xyz".to_string(), + parameter: "ABC8".to_string(), + }, + common::UserDefinedParameterType { + base: "9".to_string(), + parameter: "ABC9".to_string(), + }, + ], + }, + ), + }, + }, + }, + id: Some("CCSDS_OMM_VERS".to_string()), + version: "2.0".to_string(), + }); + } + + #[test] + fn test_parse_omm_message_xml_with_empty_object_id() { + // According to Orekit this should fail to parse due to having an empty object id. However, the XSD type of + // the object id is just xsd:string, which allows empty strings too. + + let xml = r#" + +
+ 2021-03-24T23:00:00.000 + CelesTrak +
+ + + + STARLETTE + + EARTH + TEME + UTC + SGP4 + + + + 2021-03-22T13:21:09.224928 + 13.82309053 + .0205751 + 49.8237 + 93.8140 + 224.8348 + 133.5761 + + + 0 + U + 7646 + 999 + 32997 + -.47102E-5 + -.147E-5 + 0 + + + + +
"#; + + let message = OmmType::from_xml_str(xml).unwrap(); + + assert_eq!( + message, + OmmType { + header: common::OdmHeader { + comment_list: vec![], + classification_list: vec![], + creation_date: common::EpochType("2021-03-24T23:00:00.000".to_string()), + originator: "CelesTrak".to_string(), + message_id: None, + }, + body: OmmBody { + segment: OmmSegment { + metadata: OmmMetadata { + comment_list: vec![], + object_name: "STARLETTE".to_string(), + object_id: "".to_string(), + center_name: "EARTH".to_string(), + ref_frame: "TEME".to_string(), + ref_frame_epoch: None, + time_system: "UTC".to_string(), + mean_element_theory: "SGP4".to_string(), + }, + data: OmmData { + comment_list: vec![], + mean_elements: MeanElementsType { + comment_list: vec![], + epoch: common::EpochType("2021-03-22T13:21:09.224928".to_string()), + semi_major_axis: None, + mean_motion: Some(RevType { + base: 13.82309053, + units: None, + },), + eccentricity: common::NonNegativeDouble(0.0205751,), + inclination: common::InclinationType { + base: 49.8237, + units: None, + }, + ra_of_asc_node: common::AngleType { + base: 93.8140, + + units: None, + }, + arg_of_pericenter: common::AngleType { + base: 224.8348, + units: None, + }, + mean_anomaly: common::AngleType { + base: 133.5761, + units: None, + }, + gm: None, + }, + spacecraft_parameters: None, + tle_parameters: Some(TleParametersType { + comment_list: vec![], + ephemeris_type: Some(0,), + classification_type: Some("U".to_string()), + norad_cat_id: Some(7646,), + element_set_no: Some(ElementSetNoType("999".to_string()),), + rev_at_epoch: Some(32997,), + bstar: Some(BStarType { + base: -4.7102e-6, + units: None, + },), + bterm: None, + mean_motion_dot: DRevType { + base: -1.47e-6, + units: None, + }, + mean_motion_ddot: Some(DRevType { + base: 0.0, + units: None, + },), + agom: None, + },), + covariance_matrix: None, + user_defined_parameters: None, + }, + }, + }, + id: Some("CCSDS_OMM_VERS".to_string()), + version: "2.0".to_string(), + } + ); + } + + #[test] + fn test_parse_omm_message_xml_2() { + let xml = r#" + +
+ THIS IS AN XML VERSION OF THE OMM + 2007-065T16:00:00 + NOAA + OMM 201113719185 +
+ + + + + GOES-9 + 1995-025A + EARTH + TEME + UTC + SGP/SGP4 + + + + + 2007-064T10:34:41.4264 + 1.00273272 + 0.0005013 + 3.0539 + 81.7939 + 249.2363 + 150.1602 + 398600.8 + + + 23581 + 0925 + 4316 + 0.0001 + -0.00000113 + 0.0 + + + TEME + 3.331349476038534e-04 + 4.618927349220216e-04 + 6.782421679971363e-04 + -3.070007847730449e-04 + -4.221234189514228e-04 + 3.231931992380369e-04 + -3.349365033922630e-07 + -4.686084221046758e-07 + 2.484949578400095e-07 + 4.296022805587290e-10 + -2.211832501084875e-07 + -2.864186892102733e-07 + 1.798098699846038e-07 + 2.608899201686016e-10 + 1.767514756338532e-10 + -3.041346050686871e-07 + -4.989496988610662e-07 + 3.540310904497689e-07 + 1.869263192954590e-10 + 1.008862586240695e-10 + 6.224444338635500e-10 + + + + +
"#; + + let message = OmmType::from_xml_str(xml).unwrap(); + + assert_eq!( + message, + OmmType { + header: common::OdmHeader { + comment_list: vec!["THIS IS AN XML VERSION OF THE OMM".to_string()], + classification_list: vec![], + creation_date: common::EpochType("2007-065T16:00:00".to_string()), + originator: "NOAA".to_string(), + message_id: Some("OMM 201113719185".to_string()), + }, + body: OmmBody { + segment: OmmSegment { + metadata: OmmMetadata { + comment_list: vec![], + object_name: "GOES-9".to_string(), + object_id: "1995-025A".to_string(), + center_name: "EARTH".to_string(), + ref_frame: "TEME".to_string(), + ref_frame_epoch: None, + time_system: "UTC".to_string(), + mean_element_theory: "SGP/SGP4".to_string(), + }, + data: OmmData { + comment_list: vec![], + mean_elements: MeanElementsType { + comment_list: vec![], + epoch: common::EpochType("2007-064T10:34:41.4264".to_string()), + semi_major_axis: None, + mean_motion: Some(RevType { + base: 1.00273272, + units: None, + },), + eccentricity: common::NonNegativeDouble(0.0005013,), + inclination: common::InclinationType { + base: 3.0539, + units: None, + }, + ra_of_asc_node: common::AngleType { + base: 81.7939, + units: None, + }, + arg_of_pericenter: common::AngleType { + base: 249.2363, + units: None, + }, + mean_anomaly: common::AngleType { + base: 150.1602, + units: None, + }, + gm: Some(common::GmType { + base: common::PositiveDouble(398600.8,), + units: None, + },), + }, + spacecraft_parameters: None, + tle_parameters: Some(TleParametersType { + comment_list: vec![], + ephemeris_type: None, + classification_type: None, + norad_cat_id: Some(23581,), + element_set_no: Some(ElementSetNoType("0925".to_string()),), + rev_at_epoch: Some(4316,), + bstar: Some(BStarType { + base: 0.0001, + units: None, + },), + bterm: None, + mean_motion_dot: DRevType { + base: -1.13e-6, + units: None, + }, + mean_motion_ddot: Some(DRevType { + base: 0.0, + units: None, + },), + agom: None, + },), + covariance_matrix: Some(common::OpmCovarianceMatrixType { + comment_list: vec![], + cov_ref_frame: Some("TEME".to_string()), + cx_x: common::PositionCovarianceType { + base: 0.0003331349476038534, + units: None, + }, + cy_x: common::PositionCovarianceType { + base: 0.0004618927349220216, + units: None, + }, + cy_y: common::PositionCovarianceType { + base: 0.0006782421679971363, + units: None, + }, + cz_x: common::PositionCovarianceType { + base: -0.0003070007847730449, + units: None, + }, + cz_y: common::PositionCovarianceType { + base: -0.0004221234189514228, + units: None, + }, + cz_z: common::PositionCovarianceType { + base: 0.0003231931992380369, + units: None, + }, + cx_dot_x: common::PositionVelocityCovarianceType { + base: -3.34936503392263e-7, + units: None, + }, + cx_dot_y: common::PositionVelocityCovarianceType { + base: -4.686084221046758e-7, + units: None, + }, + cx_dot_z: common::PositionVelocityCovarianceType { + base: 2.484949578400095e-7, + units: None, + }, + cx_dot_x_dot: common::VelocityCovarianceType { + base: 4.29602280558729e-10, + units: None, + }, + cy_dot_x: common::PositionVelocityCovarianceType { + base: -2.211832501084875e-7, + units: None, + }, + cy_dot_y: common::PositionVelocityCovarianceType { + base: -2.864186892102733e-7, + units: None, + }, + cy_dot_z: common::PositionVelocityCovarianceType { + base: 1.798098699846038e-7, + units: None, + }, + cy_dot_x_dot: common::VelocityCovarianceType { + base: 2.608899201686016e-10, + units: None, + }, + cy_dot_y_dot: common::VelocityCovarianceType { + base: 1.767514756338532e-10, + units: None, + }, + cz_dot_x: common::PositionVelocityCovarianceType { + base: -3.041346050686871e-7, + units: None, + }, + cz_dot_y: common::PositionVelocityCovarianceType { + base: -4.989496988610662e-7, + units: None, + }, + cz_dot_z: common::PositionVelocityCovarianceType { + base: 3.540310904497689e-7, + units: None, + }, + cz_dot_x_dot: common::VelocityCovarianceType { + base: 1.86926319295459e-10, + units: None, + }, + cz_dot_y_dot: common::VelocityCovarianceType { + base: 1.008862586240695e-10, + units: None, + }, + cz_dot_z_dot: common::VelocityCovarianceType { + base: 6.2244443386355e-10, + units: None, + }, + },), + user_defined_parameters: None, + }, + }, + }, + id: Some("CCSDS_OMM_VERS".to_string()), + version: "3.0".to_string(), + } + ); + } + + #[test] + fn test_parse_omm_message_xml_with_units() { + let xml = r#" + +
+ THIS IS AN XML VERSION OF THE OMM + 2007-065T16:00:00 + NOAA + OMM 201113719185 +
+ + + + + GOES-9 + 1995-025A + EARTH + TEME + UTC + SGP/SGP4 + + + + + mean Elements + 2007-064T10:34:41.4264 + 1.00273272 + 0.0005013 + 3.0539 + 81.7939 + 249.2363 + 150.1602 + 398600.8 + + + tle Parameters + 23581 + 0925 + 4316 + 0.0001 + -0.00000113 + 0.0 + + + covariance Matrix + TEME + 3.331349476038534e-04 + 4.618927349220216e-04 + 6.782421679971363e-04 + -3.070007847730449e-04 + -4.221234189514228e-04 + 3.231931992380369e-04 + -3.349365033922630e-07 + -4.686084221046758e-07 + 2.484949578400095e-07 + 4.296022805587290e-10 + -2.211832501084875e-07 + -2.864186892102733e-07 + 1.798098699846038e-07 + 2.608899201686016e-10 + 1.767514756338532e-10 + -3.041346050686871e-07 + -4.989496988610662e-07 + 3.540310904497689e-07 + 1.869263192954590e-10 + 1.008862586240695e-10 + 6.224444338635500e-10 + + + + +
"#; + + let message = OmmType::from_xml_str(xml).unwrap(); + + assert_eq!( + message, + OmmType { + header: common::OdmHeader { + comment_list: vec!["THIS IS AN XML VERSION OF THE OMM".to_string()], + classification_list: vec![], + creation_date: common::EpochType("2007-065T16:00:00".to_string()), + originator: "NOAA".to_string(), + message_id: Some("OMM 201113719185".to_string()), + }, + body: OmmBody { + segment: OmmSegment { + metadata: OmmMetadata { + comment_list: vec![], + object_name: "GOES-9".to_string(), + object_id: "1995-025A".to_string(), + center_name: "EARTH".to_string(), + ref_frame: "TEME".to_string(), + ref_frame_epoch: None, + time_system: "UTC".to_string(), + mean_element_theory: "SGP/SGP4".to_string(), + }, + data: OmmData { + comment_list: vec![], + mean_elements: MeanElementsType { + comment_list: vec!["mean Elements".to_string()], + epoch: common::EpochType("2007-064T10:34:41.4264".to_string()), + semi_major_axis: None, + mean_motion: Some(RevType { + base: 1.00273272, + units: Some(RevUnits("rev/day".to_string()),), + },), + eccentricity: common::NonNegativeDouble(0.0005013,), + inclination: common::InclinationType { + base: 3.0539, + units: Some(common::AngleUnits("deg".to_string()),), + }, + ra_of_asc_node: common::AngleType { + base: 81.7939, + units: Some(common::AngleUnits("deg".to_string()),), + }, + arg_of_pericenter: common::AngleType { + base: 249.2363, + units: Some(common::AngleUnits("deg".to_string()),), + }, + mean_anomaly: common::AngleType { + base: 150.1602, + units: Some(common::AngleUnits("deg".to_string()),), + }, + gm: Some(common::GmType { + base: common::PositiveDouble(398600.8,), + units: None, + },), + }, + spacecraft_parameters: None, + tle_parameters: Some(TleParametersType { + comment_list: vec!["tle Parameters".to_string()], + ephemeris_type: None, + classification_type: None, + norad_cat_id: Some(23581,), + element_set_no: Some(ElementSetNoType("0925".to_string()),), + rev_at_epoch: Some(4316,), + bstar: Some(BStarType { + base: 0.0001, + units: Some(BStarUnits("1/ER".to_string()),), + },), + bterm: None, + mean_motion_dot: DRevType { + base: -1.13e-6, + units: Some(DRevUnits("rev/day**2".to_string()),), + }, + mean_motion_ddot: Some(DRevType { + base: 0.0, + units: Some(DRevUnits("rev/day**3".to_string()),), + },), + agom: None, + },), + covariance_matrix: Some(common::OpmCovarianceMatrixType { + comment_list: vec!["covariance Matrix".to_string()], + cov_ref_frame: Some("TEME".to_string()), + cx_x: common::PositionCovarianceType { + base: 0.0003331349476038534, + units: None, + }, + cy_x: common::PositionCovarianceType { + base: 0.0004618927349220216, + units: None, + }, + cy_y: common::PositionCovarianceType { + base: 0.0006782421679971363, + units: None, + }, + cz_x: common::PositionCovarianceType { + base: -0.0003070007847730449, + units: None, + }, + cz_y: common::PositionCovarianceType { + base: -0.0004221234189514228, + units: None, + }, + cz_z: common::PositionCovarianceType { + base: 0.0003231931992380369, + units: None, + }, + cx_dot_x: common::PositionVelocityCovarianceType { + base: -3.34936503392263e-7, + units: None, + }, + cx_dot_y: common::PositionVelocityCovarianceType { + base: -4.686084221046758e-7, + units: None, + }, + cx_dot_z: common::PositionVelocityCovarianceType { + base: 2.484949578400095e-7, + units: None, + }, + cx_dot_x_dot: common::VelocityCovarianceType { + base: 4.29602280558729e-10, + units: None, + }, + cy_dot_x: common::PositionVelocityCovarianceType { + base: -2.211832501084875e-7, + units: None, + }, + cy_dot_y: common::PositionVelocityCovarianceType { + base: -2.864186892102733e-7, + units: None, + }, + cy_dot_z: common::PositionVelocityCovarianceType { + base: 1.798098699846038e-7, + units: None, + }, + cy_dot_x_dot: common::VelocityCovarianceType { + base: 2.608899201686016e-10, + units: None, + }, + cy_dot_y_dot: common::VelocityCovarianceType { + base: 1.767514756338532e-10, + units: None, + }, + cz_dot_x: common::PositionVelocityCovarianceType { + base: -3.041346050686871e-7, + units: None, + }, + cz_dot_y: common::PositionVelocityCovarianceType { + base: -4.989496988610662e-7, + units: None, + }, + cz_dot_z: common::PositionVelocityCovarianceType { + base: 3.540310904497689e-7, + units: None, + }, + cz_dot_x_dot: common::VelocityCovarianceType { + base: 1.86926319295459e-10, + units: None, + }, + cz_dot_y_dot: common::VelocityCovarianceType { + base: 1.008862586240695e-10, + units: None, + }, + cz_dot_z_dot: common::VelocityCovarianceType { + base: 6.2244443386355e-10, + units: None, + }, + },), + user_defined_parameters: None, + }, + }, + }, + id: Some("CCSDS_OMM_VERS".to_string()), + version: "3.0".to_string(), + } + ); + } + + #[test] + fn test_parse_omm_message_xml_3() { + let xml = r#" + +
+ 2021-03-24T23:00:00.000 + CelesTrak +
+ + + + STARLETTE + 1975-010A + EARTH + TEME + UTC + SGP4 + + + + 2008-09-20T12:25:40.104192 + 15.72125391 + 0.0006703 + 51.6416 + 247.4627 + 130.5360 + 325.0288 + 398600.8 + + + 0 + U + 7646 + 999 + 32997 + -.47102E-5 + -.147E-5 + 0 + + + foo enters + a bar + + + + +
"#; + + let message = OmmType::from_xml_str(xml).unwrap(); + + assert_eq!( + message, + OmmType { + header: common::OdmHeader { + comment_list: vec![], + classification_list: vec![], + creation_date: common::EpochType("2021-03-24T23:00:00.000".to_string()), + originator: "CelesTrak".to_string(), + message_id: None, + }, + body: OmmBody { + segment: OmmSegment { + metadata: OmmMetadata { + comment_list: vec![], + object_name: "STARLETTE".to_string(), + object_id: "1975-010A".to_string(), + center_name: "EARTH".to_string(), + ref_frame: "TEME".to_string(), + ref_frame_epoch: None, + time_system: "UTC".to_string(), + mean_element_theory: "SGP4".to_string(), + }, + data: OmmData { + comment_list: vec![], + mean_elements: MeanElementsType { + comment_list: vec![], + epoch: common::EpochType("2008-09-20T12:25:40.104192".to_string()), + semi_major_axis: None, + mean_motion: Some(RevType { + base: 15.72125391, + units: Some(RevUnits("rev/day".to_string()),), + },), + eccentricity: common::NonNegativeDouble(0.0006703,), + inclination: common::InclinationType { + base: 51.6416, + units: Some(common::AngleUnits("deg".to_string()),), + }, + ra_of_asc_node: common::AngleType { + base: 247.4627, + units: Some(common::AngleUnits("deg".to_string()),), + }, + arg_of_pericenter: common::AngleType { + base: 130.536, + units: Some(common::AngleUnits("deg".to_string()),), + }, + mean_anomaly: common::AngleType { + base: 325.0288, + units: Some(common::AngleUnits("deg".to_string()),), + }, + gm: Some(common::GmType { + base: common::PositiveDouble(398600.8,), + units: Some(common::GmUnits("km**3/s**2".to_string()),), + },), + }, + spacecraft_parameters: None, + tle_parameters: Some(TleParametersType { + comment_list: vec![], + ephemeris_type: Some(0,), + classification_type: Some("U".to_string()), + norad_cat_id: Some(7646,), + element_set_no: Some(ElementSetNoType("999".to_string()),), + rev_at_epoch: Some(32997,), + bstar: Some(BStarType { + base: -4.7102e-6, + units: None, + },), + bterm: None, + mean_motion_dot: DRevType { + base: -1.47e-6, + units: None, + }, + mean_motion_ddot: Some(DRevType { + base: 0.0, + units: None, + },), + agom: None, + },), + covariance_matrix: None, + user_defined_parameters: Some(common::UserDefinedType { + comment_list: vec![], + user_defined_list: vec![ + common::UserDefinedParameterType { + base: "foo enters".to_string(), + parameter: "FOO".to_string(), + }, + common::UserDefinedParameterType { + base: "a bar".to_string(), + parameter: "BAR".to_string(), + }, + ], + },), + }, + }, + }, + id: Some("CCSDS_OMM_VERS".to_string()), + version: "2.0".to_string(), + } + ); + } + + #[test] + fn test_parse_omm_message_spurious() { + let xml = r#" + +
+ 2021-03-24T23:00:00.000 + CelesTrak +
+ + + + STARLETTE + 1975-010A + EARTH + TEME + UTC + SGP4 + + + second metadata is an error + + + +
"#; + + let message = OmmType::from_xml_str(xml); + + assert!(message.is_err()); + } + + #[test] + fn test_parse_omm_message_kvn() { + //@TOOD add back user defined stuff + let kvn = r#"CCSDS_OMM_VERS = 3.0 + COMMENT this is a comment + COMMENT here is another one + CREATION_DATE = 2007-06-05T16:00:00 + ORIGINATOR = NOAA/USA + COMMENT this comment doesn't say much + OBJECT_NAME = GOES 9 + OBJECT_ID = 1995-025A + CENTER_NAME = EARTH + REF_FRAME = TOD + REF_FRAME_EPOCH = 2000-01-03T10:34:00 + TIME_SYSTEM = MRT + MEAN_ELEMENT_THEORY = SOME THEORY + COMMENT the following data is what we're looking for + EPOCH = 2000-01-05T10:00:00 + SEMI_MAJOR_AXIS = 6800 + ECCENTRICITY = 0.0005013 + INCLINATION = 3.0539 + RA_OF_ASC_NODE = 81.7939 + ARG_OF_PERICENTER = 249.2363 + MEAN_ANOMALY = 150.1602 + COMMENT spacecraft data + MASS = 300 + SOLAR_RAD_AREA = 5 + SOLAR_RAD_COEFF = 0.001 + DRAG_AREA = 4 + DRAG_COEFF = 0.002 + COMMENT Covariance matrix + COV_REF_FRAME = TNW + CX_X = 3.331349476038534e-04 + CY_X = 4.618927349220216e-04 + CY_Y = 6.782421679971363e-04 + CZ_X = -3.070007847730449e-04 + CZ_Y = -4.221234189514228e-04 + CZ_Z = 3.231931992380369e-04 + CX_DOT_X = -3.349365033922630e-07 + CX_DOT_Y = -4.686084221046758e-07 + CX_DOT_Z = 2.484949578400095e-07 + CX_DOT_X_DOT = 4.296022805587290e-10 + CY_DOT_X = -2.211832501084875e-07 + CY_DOT_Y = -2.864186892102733e-07 + CY_DOT_Z = 1.798098699846038e-07 + CY_DOT_X_DOT = 2.608899201686016e-10 + CY_DOT_Y_DOT = 1.767514756338532e-10 + CZ_DOT_X = -3.041346050686871e-07 + CZ_DOT_Y = -4.989496988610662e-07 + CZ_DOT_Z = 3.540310904497689e-07 + CZ_DOT_X_DOT = 1.869263192954590e-10 + CZ_DOT_Y_DOT = 1.008862586240695e-10 + CZ_DOT_Z_DOT = 6.224444338635500e-10"#; + + assert_eq!( + crate::ndm::kvn::KvnDeserializer::from_kvn_str(&kvn), + Ok(OmmType { + header: common::OdmHeader { + comment_list: vec![ + "this is a comment".to_string(), + "here is another one".to_string(), + ], + classification_list: vec![], + creation_date: common::EpochType("2007-06-05T16:00:00".to_string()), + originator: "NOAA/USA".to_string(), + message_id: None, + }, + body: OmmBody { + segment: OmmSegment { + metadata: OmmMetadata { + comment_list: vec!["this comment doesn't say much".to_string()], + object_name: "GOES 9".to_string(), + object_id: "1995-025A".to_string(), + center_name: "EARTH".to_string(), + ref_frame: "TOD".to_string(), + ref_frame_epoch: Some(common::EpochType( + "2000-01-03T10:34:00".to_string(), + )), + time_system: "MRT".to_string(), + mean_element_theory: "SOME THEORY".to_string(), + }, + data: OmmData { + comment_list: vec![ + "the following data is what we're looking for".to_string(), + ], + mean_elements: MeanElementsType { + comment_list: vec![], + epoch: common::EpochType("2000-01-05T10:00:00".to_string()), + semi_major_axis: Some(common::DistanceType { + base: 6800.0, + units: None, + },), + mean_motion: None, + eccentricity: common::NonNegativeDouble(0.0005013,), + inclination: common::InclinationType { + base: 3.0539, + units: None, + }, + ra_of_asc_node: common::AngleType { + base: 81.7939, + units: None, + }, + arg_of_pericenter: common::AngleType { + base: 249.2363, + units: None, + }, + mean_anomaly: common::AngleType { + base: 150.1602, + units: None, + }, + gm: None, + }, + spacecraft_parameters: Some(common::SpacecraftParametersType { + comment_list: vec!["spacecraft data".to_string()], + mass: Some(common::MassType { + base: common::NonNegativeDouble(300.0,), + units: None, + },), + solar_rad_area: Some(common::AreaType { + base: common::NonNegativeDouble(5.0,), + units: None, + },), + solar_rad_coeff: Some(common::NonNegativeDouble(0.001,)), + drag_area: Some(common::AreaType { + base: common::NonNegativeDouble(4.0,), + units: None, + },), + drag_coeff: Some(common::NonNegativeDouble(0.002,)), + },), + tle_parameters: None, + covariance_matrix: Some(common::OpmCovarianceMatrixType { + comment_list: vec![], + cov_ref_frame: Some("TNW".to_string()), + cx_x: common::PositionCovarianceType { + base: 0.0003331349476038534, + units: None, + }, + cy_x: common::PositionCovarianceType { + base: 0.0004618927349220216, + units: None, + }, + cy_y: common::PositionCovarianceType { + base: 0.0006782421679971363, + units: None, + }, + cz_x: common::PositionCovarianceType { + base: -0.0003070007847730449, + units: None, + }, + cz_y: common::PositionCovarianceType { + base: -0.0004221234189514228, + units: None, + }, + cz_z: common::PositionCovarianceType { + base: 0.0003231931992380369, + units: None, + }, + cx_dot_x: common::PositionVelocityCovarianceType { + base: -3.34936503392263e-7, + units: None, + }, + cx_dot_y: common::PositionVelocityCovarianceType { + base: -4.686084221046758e-7, + units: None, + }, + cx_dot_z: common::PositionVelocityCovarianceType { + base: 2.484949578400095e-7, + units: None, + }, + cx_dot_x_dot: common::VelocityCovarianceType { + base: 4.29602280558729e-10, + units: None, + }, + cy_dot_x: common::PositionVelocityCovarianceType { + base: -2.211832501084875e-7, + units: None, + }, + cy_dot_y: common::PositionVelocityCovarianceType { + base: -2.864186892102733e-7, + units: None, + }, + cy_dot_z: common::PositionVelocityCovarianceType { + base: 1.798098699846038e-7, + units: None, + }, + cy_dot_x_dot: common::VelocityCovarianceType { + base: 2.608899201686016e-10, + units: None, + }, + cy_dot_y_dot: common::VelocityCovarianceType { + base: 1.767514756338532e-10, + units: None, + }, + cz_dot_x: common::PositionVelocityCovarianceType { + base: -3.041346050686871e-7, + units: None, + }, + cz_dot_y: common::PositionVelocityCovarianceType { + base: -4.989496988610662e-7, + units: None, + }, + cz_dot_z: common::PositionVelocityCovarianceType { + base: 3.540310904497689e-7, + units: None, + }, + cz_dot_x_dot: common::VelocityCovarianceType { + base: 1.86926319295459e-10, + units: None, + }, + cz_dot_y_dot: common::VelocityCovarianceType { + base: 1.008862586240695e-10, + units: None, + }, + cz_dot_z_dot: common::VelocityCovarianceType { + base: 6.2244443386355e-10, + units: None, + }, + },), + user_defined_parameters: None, + }, + }, + }, + id: None, + version: "3.0".to_string(), + },) + ); + } +} diff --git a/crates/lox-io/src/ndm/opm.rs b/crates/lox-io/src/ndm/opm.rs new file mode 100644 index 00000000..e8a53aff --- /dev/null +++ b/crates/lox-io/src/ndm/opm.rs @@ -0,0 +1,975 @@ +/* + * Copyright (c) 2023. Helge Eichhorn and the LOX contributors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + */ + +//! Deserializers for XML and KVN CCSDS Orbit Parameter Message +//! +//! To deserialize an XML message: +//! +//! ``` +//! # let xml = r#" +//! # +//! # +//! #
+//! # THIS IS AN XML VERSION OF THE OPM +//! # 2001-11-06T09:23:57 +//! # JAXA +//! # OPM 201113719185 +//! #
+//! # +//! # +//! # +//! # GEOCENTRIC, CARTESIAN, EARTH FIXED +//! # OSPREY 5 +//! # 1998-999A +//! # EARTH +//! # TOD +//! # 1998-12-18T14:28:15.1172 +//! # UTC +//! # +//! # +//! # +//! # 2008-09-20T12:25:40.104192 +//! # 4086.147180 +//! # -994.936814 +//! # 5250.678791 +//! # 2.511071 +//! # 7.255240 +//! # -0.583165 +//! # +//! # +//! # 6730.96 +//! # 0.0006703 +//! # 51.6416 +//! # 247.463 +//! # 130.536 +//! # 324.985 +//! # 398600.9368 +//! # +//! # +//! # 3000.000000 +//! # 18.770000 +//! # 1.000000 +//! # 18.770000 +//! # 2.500000 +//! # +//! # +//! # ITRF1997 +//! # 0.316 +//! # 0.722 +//! # 0.518 +//! # 0.202 +//! # 0.715 +//! # 0.002 +//! # 0.912 +//! # 0.306 +//! # 0.276 +//! # 0.797 +//! # 0.562 +//! # 0.899 +//! # 0.022 +//! # 0.079 +//! # 0.415 +//! # 0.245 +//! # 0.965 +//! # 0.950 +//! # 0.435 +//! # 0.621 +//! # 0.991 +//! # +//! # +//! # Maneuver 1 +//! # 2008-09-20T12:41:09.984493 +//! # 180.000 +//! # -0.001 +//! # RSW +//! # 0.000000 +//! # 0.280000 +//! # 0.000000 +//! # +//! # +//! # 2008-09-20T13:33:11.374985 +//! # 180.000 +//! # -0.001 +//! # RSW +//! # 0.000000 +//! # 0.270000 +//! # 0.000000 +//! # +//! # +//! # +//! # +//! #
"#; +//! # +//! # use lox_io::ndm::opm::OpmType; +//! use lox_io::ndm::xml::FromXmlStr; +//! +//! let message: OpmType = quick_xml::de::from_str(xml).unwrap(); +//! ``` +//! +//! To deserialize a KVN message: +//! ``` +//! # let kvn = r#"CCSDS_OPM_VERS = 3.0 +//! # COMMENT Generated by GSOC, R. Kiehling +//! # COMMENT Current intermediate orbit IO2 and maneuver planning data +//! # CREATION_DATE = 2021-06-03T05:33:00.123 +//! # ORIGINATOR = GSOC +//! # OBJECT_NAME = EUTELSAT W4 +//! # OBJECT_ID = 2021-028A +//! # CENTER_NAME = EARTH +//! # REF_FRAME = TOD +//! # TIME_SYSTEM = UTC +//! # COMMENT State Vector +//! # EPOCH = 2021-06-03T00:00:00.000 +//! # X = 6655.9942 [km] +//! # Y = -40218.5751 [km] +//! # Z = -82.9177 [km] +//! # X_DOT = 3.11548208 [km/s] +//! # Y_DOT = 0.47042605 [km/s] +//! # Z_DOT = -0.00101495 [km/s] +//! # COMMENT Keplerian elements +//! # SEMI_MAJOR_AXIS = 41399.5123 [km] +//! # ECCENTRICITY = 0.020842611 +//! # INCLINATION = 0.117746 [deg] +//! # RA_OF_ASC_NODE = 17.604721 [deg] +//! # ARG_OF_PERICENTER = 218.242943 [deg] +//! # TRUE_ANOMALY = 41.922339 [deg] +//! # GM = 398600.4415 [km**3/s**2] +//! # COMMENT Spacecraft parameters +//! # MASS = 1913.000 [kg] +//! # SOLAR_RAD_AREA = 10.000 [m**2] +//! # SOLAR_RAD_COEFF = 1.300 +//! # DRAG_AREA = 10.000 [m**2] +//! # DRAG_COEFF = 2.300 +//! # COMMENT 2 planned maneuvers +//! # COMMENT First maneuver: AMF-3 +//! # COMMENT Non-impulsive, thrust direction fixed in inertial frame +//! # MAN_EPOCH_IGNITION = 2021-06-03T09:00:34.1 +//! # MAN_DURATION = 132.60 [s] +//! # MAN_DELTA_MASS = -18.418 [kg] +//! # MAN_REF_FRAME = EME2000 +//! # MAN_DV_1 = -0.02325700 [km/s] +//! # MAN_DV_2 = 0.01683160 [km/s] +//! # MAN_DV_3 = -0.00893444 [km/s] +//! # COMMENT Second maneuver: first station acquisition maneuver +//! # COMMENT impulsive, thrust direction fixed in RTN frame +//! # MAN_EPOCH_IGNITION = 2021-06-05T18:59:21.0 +//! # MAN_DURATION = 0.00 [s] +//! # MAN_DELTA_MASS = -1.469 [kg] +//! # MAN_REF_FRAME = RTN +//! # MAN_DV_1 = 0.00101500 [km/s] +//! # MAN_DV_2 = -0.00187300 [km/s] +//! # MAN_DV_3 = 0.00000000 [km/s]"#; +//! # +//! # use lox_io::ndm::opm::OpmType; +//! use lox_io::ndm::kvn::KvnDeserializer; +//! +//! let message: OpmType = KvnDeserializer::from_kvn_str(&kvn).unwrap(); +//! ``` + +// This file is partially generated with xml-schema-derive from the XSD schema +// published by CCSDS. Adaptations have been made to simplify the types or +// allow to simplify the implementation of the KVN parser. + +use serde; + +use super::common; + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct OpmType { + #[serde(rename = "@id")] + // Marked as option for the KVN deserializer + pub id: Option, + #[serde(rename = "@version")] + pub version: String, + #[serde(rename = "header")] + pub header: common::OdmHeader, + #[serde(rename = "body")] + pub body: OpmBody, +} + +impl crate::ndm::xml::FromXmlStr<'_> for OpmType {} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct OpmBody { + #[serde(rename = "segment")] + pub segment: OpmSegment, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct OpmSegment { + #[serde(rename = "metadata")] + pub metadata: OpmMetadata, + #[serde(rename = "data")] + pub data: OpmData, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct OpmMetadata { + #[serde(rename = "COMMENT")] + pub comment_list: Vec, + #[serde(rename = "OBJECT_NAME")] + pub object_name: String, + #[serde(rename = "OBJECT_ID")] + pub object_id: String, + #[serde(rename = "CENTER_NAME")] + pub center_name: String, + #[serde(rename = "REF_FRAME")] + pub ref_frame: String, + #[serde(rename = "REF_FRAME_EPOCH")] + pub ref_frame_epoch: Option, + #[serde(rename = "TIME_SYSTEM")] + pub time_system: String, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct OpmData { + #[serde(rename = "COMMENT")] + pub comment_list: Vec, + #[serde(rename = "stateVector")] + pub state_vector: common::StateVectorType, + #[serde(rename = "keplerianElements")] + pub keplerian_elements: Option, + #[serde(rename = "spacecraftParameters")] + pub spacecraft_parameters: Option, + #[serde(rename = "covarianceMatrix")] + pub covariance_matrix: Option, + #[serde(rename = "maneuverParameters")] + pub maneuver_parameters_list: Vec, + #[serde(rename = "userDefinedParameters")] + pub user_defined_parameters: Option, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct KeplerianElementsType { + #[serde(rename = "COMMENT")] + pub comment_list: Vec, + #[serde(rename = "SEMI_MAJOR_AXIS")] + pub semi_major_axis: common::DistanceType, + #[serde(rename = "ECCENTRICITY")] + pub eccentricity: common::NonNegativeDouble, + #[serde(rename = "INCLINATION")] + pub inclination: common::InclinationType, + #[serde(rename = "RA_OF_ASC_NODE")] + pub ra_of_asc_node: common::AngleType, + #[serde(rename = "ARG_OF_PERICENTER")] + pub arg_of_pericenter: common::AngleType, + #[serde(rename = "TRUE_ANOMALY")] + pub true_anomaly: Option, + #[serde(rename = "MEAN_ANOMALY")] + pub mean_anomaly: Option, + #[serde(rename = "GM")] + pub gm: common::GmType, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + serde::Deserialize, + serde::Serialize, + lox_derive::KvnDeserialize, +)] +#[serde(default)] +pub struct ManeuverParametersType { + #[serde(rename = "COMMENT")] + pub comment_list: Vec, + #[serde(rename = "MAN_EPOCH_IGNITION")] + pub man_epoch_ignition: common::EpochType, + #[serde(rename = "MAN_DURATION")] + pub man_duration: common::DurationType, + #[serde(rename = "MAN_DELTA_MASS")] + pub man_delta_mass: common::DeltamassType, + #[serde(rename = "MAN_REF_FRAME")] + pub man_ref_frame: String, + #[serde(rename = "MAN_DV_1")] + pub man_dv_1: common::VelocityType, + #[serde(rename = "MAN_DV_2")] + pub man_dv_2: common::VelocityType, + #[serde(rename = "MAN_DV_3")] + pub man_dv_3: common::VelocityType, +} + +#[cfg(test)] +mod test { + use crate::ndm::xml::FromXmlStr; + + use super::*; + + #[test] + fn test_parse_opm_message_xml() { + let xml = r#" + + +
+ THIS IS AN XML VERSION OF THE OPM + 2001-11-06T09:23:57 + JAXA + OPM 201113719185 +
+ + + + GEOCENTRIC, CARTESIAN, EARTH FIXED + OSPREY 5 + 1998-999A + EARTH + TOD + 1998-12-18T14:28:15.1172 + UTC + + + + 2008-09-20T12:25:40.104192 + 4086.147180 + -994.936814 + 5250.678791 + 2.511071 + 7.255240 + -0.583165 + + + 6730.96 + 0.0006703 + 51.6416 + 247.463 + 130.536 + 324.985 + 398600.9368 + + + 3000.000000 + 18.770000 + 1.000000 + 18.770000 + 2.500000 + + + ITRF1997 + 0.316 + 0.722 + 0.518 + 0.202 + 0.715 + 0.002 + 0.912 + 0.306 + 0.276 + 0.797 + 0.562 + 0.899 + 0.022 + 0.079 + 0.415 + 0.245 + 0.965 + 0.950 + 0.435 + 0.621 + 0.991 + + + Maneuver 1 + 2008-09-20T12:41:09.984493 + 180.000 + -0.001 + RSW + 0.000000 + 0.280000 + 0.000000 + + + 2008-09-20T13:33:11.374985 + 180.000 + -0.001 + RSW + 0.000000 + 0.270000 + 0.000000 + + + + +
"#; + + let message = OpmType::from_xml_str(xml).unwrap(); + + assert_eq!( + message, + OpmType { + header: common::OdmHeader { + comment_list: vec!["THIS IS AN XML VERSION OF THE OPM".to_string()], + classification_list: vec![], + creation_date: common::EpochType("2001-11-06T09:23:57".to_string()), + originator: "JAXA".to_string(), + message_id: Some("OPM 201113719185".to_string()), + }, + body: OpmBody { + segment: OpmSegment { + metadata: OpmMetadata { + comment_list: vec!["GEOCENTRIC, CARTESIAN, EARTH FIXED".to_string()], + object_name: "OSPREY 5".to_string(), + object_id: "1998-999A".to_string(), + center_name: "EARTH".to_string(), + ref_frame: "TOD".to_string(), + ref_frame_epoch: Some(common::EpochType( + "1998-12-18T14:28:15.1172".to_string(), + )), + time_system: "UTC".to_string(), + }, + data: OpmData { + comment_list: vec![], + state_vector: common::StateVectorType { + comment_list: vec![], + epoch: common::EpochType("2008-09-20T12:25:40.104192".to_string()), + x: common::PositionType { + base: 4086.14718, + units: Some(common::PositionUnits("km".to_string()),), + }, + y: common::PositionType { + base: -994.936814, + units: Some(common::PositionUnits("km".to_string()),), + }, + z: common::PositionType { + base: 5250.678791, + units: Some(common::PositionUnits("km".to_string()),), + }, + x_dot: common::VelocityType { + base: 2.511071, + units: Some(common::VelocityUnits("km/s".to_string()),), + }, + y_dot: common::VelocityType { + base: 7.25524, + units: Some(common::VelocityUnits("km/s".to_string()),), + }, + z_dot: common::VelocityType { + base: -0.583165, + units: Some(common::VelocityUnits("km/s".to_string()),), + }, + }, + keplerian_elements: Some(KeplerianElementsType { + comment_list: vec![], + semi_major_axis: common::DistanceType { + base: 6730.96, + units: Some(common::PositionUnits("km".to_string()),), + }, + eccentricity: common::NonNegativeDouble(0.0006703,), + inclination: common::InclinationType { + base: 51.6416, + units: Some(common::AngleUnits("deg".to_string()),), + }, + ra_of_asc_node: common::AngleType { + base: 247.463, + units: Some(common::AngleUnits("deg".to_string()),), + }, + arg_of_pericenter: common::AngleType { + base: 130.536, + units: Some(common::AngleUnits("deg".to_string()),), + }, + true_anomaly: Some(common::AngleType { + base: 324.985, + units: Some(common::AngleUnits("deg".to_string()),), + },), + mean_anomaly: None, + gm: common::GmType { + base: common::PositiveDouble(398600.9368,), + units: Some(common::GmUnits("km**3/s**2".to_string()),), + }, + },), + spacecraft_parameters: Some(common::SpacecraftParametersType { + comment_list: vec![], + mass: Some(common::MassType { + base: common::NonNegativeDouble(3000.0,), + units: None, + },), + solar_rad_area: Some(common::AreaType { + base: common::NonNegativeDouble(18.77,), + units: None, + },), + solar_rad_coeff: Some(common::NonNegativeDouble(1.0,)), + drag_area: Some(common::AreaType { + base: common::NonNegativeDouble(18.77,), + units: None, + },), + drag_coeff: Some(common::NonNegativeDouble(2.5,)), + },), + covariance_matrix: Some(common::OpmCovarianceMatrixType { + comment_list: vec![], + cov_ref_frame: Some("ITRF1997".to_string()), + cx_x: common::PositionCovarianceType { + base: 0.316, + units: None, + }, + cy_x: common::PositionCovarianceType { + base: 0.722, + units: None, + }, + cy_y: common::PositionCovarianceType { + base: 0.518, + units: None, + }, + cz_x: common::PositionCovarianceType { + base: 0.202, + units: None, + }, + cz_y: common::PositionCovarianceType { + base: 0.715, + units: None, + }, + cz_z: common::PositionCovarianceType { + base: 0.002, + units: None, + }, + cx_dot_x: common::PositionVelocityCovarianceType { + base: 0.912, + units: None, + }, + cx_dot_y: common::PositionVelocityCovarianceType { + base: 0.306, + units: None, + }, + cx_dot_z: common::PositionVelocityCovarianceType { + base: 0.276, + units: None, + }, + cx_dot_x_dot: common::VelocityCovarianceType { + base: 0.797, + units: None, + }, + cy_dot_x: common::PositionVelocityCovarianceType { + base: 0.562, + units: None, + }, + cy_dot_y: common::PositionVelocityCovarianceType { + base: 0.899, + units: None, + }, + cy_dot_z: common::PositionVelocityCovarianceType { + base: 0.022, + units: None, + }, + cy_dot_x_dot: common::VelocityCovarianceType { + base: 0.079, + units: None, + }, + cy_dot_y_dot: common::VelocityCovarianceType { + base: 0.415, + units: None, + }, + cz_dot_x: common::PositionVelocityCovarianceType { + base: 0.245, + units: None, + }, + cz_dot_y: common::PositionVelocityCovarianceType { + base: 0.965, + units: None, + }, + cz_dot_z: common::PositionVelocityCovarianceType { + base: 0.95, + units: None, + }, + cz_dot_x_dot: common::VelocityCovarianceType { + base: 0.435, + units: None, + }, + cz_dot_y_dot: common::VelocityCovarianceType { + base: 0.621, + units: None, + }, + cz_dot_z_dot: common::VelocityCovarianceType { + base: 0.991, + units: None, + }, + },), + maneuver_parameters_list: vec![ + ManeuverParametersType { + comment_list: vec!["Maneuver 1".to_string()], + man_epoch_ignition: common::EpochType( + "2008-09-20T12:41:09.984493".to_string(), + ), + man_duration: common::DurationType { + base: common::NonNegativeDouble(180.0,), + units: Some(common::TimeUnits("s".to_string()),), + }, + man_delta_mass: common::DeltamassType { + base: common::NegativeDouble(-0.001,), + units: Some(common::MassUnits("kg".to_string()),), + }, + man_ref_frame: "RSW".to_string(), + man_dv_1: common::VelocityType { + base: 0.0, + units: Some(common::VelocityUnits("km/s".to_string()),), + }, + man_dv_2: common::VelocityType { + base: 0.28, + units: Some(common::VelocityUnits("km/s".to_string()),), + }, + man_dv_3: common::VelocityType { + base: 0.0, + units: Some(common::VelocityUnits("km/s".to_string()),), + }, + }, + ManeuverParametersType { + comment_list: vec![], + man_epoch_ignition: common::EpochType( + "2008-09-20T13:33:11.374985".to_string(), + ), + man_duration: common::DurationType { + base: common::NonNegativeDouble(180.0,), + units: Some(common::TimeUnits("s".to_string()),), + }, + man_delta_mass: common::DeltamassType { + base: common::NegativeDouble(-0.001,), + units: Some(common::MassUnits("kg".to_string()),), + }, + man_ref_frame: "RSW".to_string(), + man_dv_1: common::VelocityType { + base: 0.0, + units: Some(common::VelocityUnits("km/s".to_string()),), + }, + man_dv_2: common::VelocityType { + base: 0.27, + units: Some(common::VelocityUnits("km/s".to_string()),), + }, + man_dv_3: common::VelocityType { + base: 0.0, + units: Some(common::VelocityUnits("km/s".to_string()),), + }, + }, + ], + user_defined_parameters: None, + }, + }, + }, + id: Some("CCSDS_OPM_VERS".to_string()), + version: "3.0".to_string(), + } + ); + } + + #[test] + fn test_parse_opm_message_spurious() { + let xml = r#" + + +
+ THIS IS AN XML VERSION OF THE OPM + 2001-11-06T09:23:57 + JAXA + OPM 201113719185 +
+ + + + GEOCENTRIC, CARTESIAN, EARTH FIXED + OSPREY 5 + 1998-999A + EARTH + TOD + 1998-12-18T14:28:15.1172 + UTC + + + second metadata is an error + + + +
"#; + + let message: Result = OpmType::from_xml_str(xml); + + assert!(message.is_err()); + } + + #[test] + fn test_parse_opm_message_kvn() { + //@TOOD add user defined stuff + let kvn = r#"CCSDS_OPM_VERS = 3.0 +COMMENT Generated by GSOC, R. Kiehling +COMMENT Current intermediate orbit IO2 and maneuver planning data +CREATION_DATE = 2021-06-03T05:33:00.123 +ORIGINATOR = GSOC +OBJECT_NAME = EUTELSAT W4 +OBJECT_ID = 2021-028A +CENTER_NAME = EARTH +REF_FRAME = TOD +TIME_SYSTEM = UTC +COMMENT State Vector +EPOCH = 2021-06-03T00:00:00.000 +X = 6655.9942 [km] +Y = -40218.5751 [km] +Z = -82.9177 [km] +X_DOT = 3.11548208 [km/s] +Y_DOT = 0.47042605 [km/s] +Z_DOT = -0.00101495 [km/s] +COMMENT Keplerian elements +SEMI_MAJOR_AXIS = 41399.5123 [km] +ECCENTRICITY = 0.020842611 +INCLINATION = 0.117746 [deg] +RA_OF_ASC_NODE = 17.604721 [deg] +ARG_OF_PERICENTER = 218.242943 [deg] +TRUE_ANOMALY = 41.922339 [deg] +GM = 398600.4415 [km**3/s**2] +COMMENT Spacecraft parameters +MASS = 1913.000 [kg] +SOLAR_RAD_AREA = 10.000 [m**2] +SOLAR_RAD_COEFF = 1.300 +DRAG_AREA = 10.000 [m**2] +DRAG_COEFF = 2.300 +COMMENT 2 planned maneuvers +COMMENT First maneuver: AMF-3 +COMMENT Non-impulsive, thrust direction fixed in inertial frame +MAN_EPOCH_IGNITION = 2021-06-03T09:00:34.1 +MAN_DURATION = 132.60 [s] +MAN_DELTA_MASS = -18.418 [kg] +MAN_REF_FRAME = EME2000 +MAN_DV_1 = -0.02325700 [km/s] +MAN_DV_2 = 0.01683160 [km/s] +MAN_DV_3 = -0.00893444 [km/s] +COMMENT Second maneuver: first station acquisition maneuver +COMMENT impulsive, thrust direction fixed in RTN frame +MAN_EPOCH_IGNITION = 2021-06-05T18:59:21.0 +MAN_DURATION = 0.00 [s] +MAN_DELTA_MASS = -1.469 [kg] +MAN_REF_FRAME = RTN +MAN_DV_1 = 0.00101500 [km/s] +MAN_DV_2 = -0.00187300 [km/s] +MAN_DV_3 = 0.00000000 [km/s]"#; + + assert_eq!( + crate::ndm::kvn::KvnDeserializer::from_kvn_str(&kvn), + Ok(OpmType { + header: common::OdmHeader { + comment_list: vec![ + "Generated by GSOC, R. Kiehling".to_string(), + "Current intermediate orbit IO2 and maneuver planning data".to_string(), + ], + classification_list: vec![], + creation_date: common::EpochType("2021-06-03T05:33:00.123".to_string()), + originator: "GSOC".to_string(), + message_id: None, + }, + body: OpmBody { + segment: OpmSegment { + metadata: OpmMetadata { + comment_list: vec![], + object_name: "EUTELSAT W4".to_string(), + object_id: "2021-028A".to_string(), + center_name: "EARTH".to_string(), + ref_frame: "TOD".to_string(), + ref_frame_epoch: None, + time_system: "UTC".to_string(), + }, + data: OpmData { + comment_list: vec!["State Vector".to_string()], + state_vector: common::StateVectorType { + comment_list: vec![], + epoch: common::EpochType("2021-06-03T00:00:00.000".to_string()), + x: common::PositionType { + base: 6655.9942, + units: Some(common::PositionUnits("km".to_string()),), + }, + y: common::PositionType { + base: -40218.5751, + units: Some(common::PositionUnits("km".to_string()),), + }, + z: common::PositionType { + base: -82.9177, + units: Some(common::PositionUnits("km".to_string()),), + }, + x_dot: common::VelocityType { + base: 3.11548208, + units: Some(common::VelocityUnits("km/s".to_string()),), + }, + y_dot: common::VelocityType { + base: 0.47042605, + units: Some(common::VelocityUnits("km/s".to_string()),), + }, + z_dot: common::VelocityType { + base: -0.00101495, + units: Some(common::VelocityUnits("km/s".to_string()),), + }, + }, + keplerian_elements: Some(KeplerianElementsType { + comment_list: vec!["Keplerian elements".to_string()], + semi_major_axis: common::DistanceType { + base: 41399.5123, + units: Some(common::PositionUnits("km".to_string()),), + }, + eccentricity: common::NonNegativeDouble(0.020842611,), + inclination: common::InclinationType { + base: 0.117746, + units: Some(common::AngleUnits("deg".to_string()),), + }, + ra_of_asc_node: common::AngleType { + base: 17.604721, + units: Some(common::AngleUnits("deg".to_string()),), + }, + arg_of_pericenter: common::AngleType { + base: 218.242943, + units: Some(common::AngleUnits("deg".to_string()),), + }, + true_anomaly: Some(common::AngleType { + base: 41.922339, + units: Some(common::AngleUnits("deg".to_string()),), + },), + mean_anomaly: None, + gm: common::GmType { + base: common::PositiveDouble(398600.4415,), + units: Some(common::GmUnits("km**3/s**2".to_string()),), + }, + },), + spacecraft_parameters: Some(common::SpacecraftParametersType { + comment_list: vec!["Spacecraft parameters".to_string()], + mass: Some(common::MassType { + base: common::NonNegativeDouble(1913.0,), + units: Some(common::MassUnits("kg".to_string()),), + },), + solar_rad_area: Some(common::AreaType { + base: common::NonNegativeDouble(10.0,), + units: Some(common::AreaUnits("m**2".to_string()),), + },), + solar_rad_coeff: Some(common::NonNegativeDouble(1.3,)), + drag_area: Some(common::AreaType { + base: common::NonNegativeDouble(10.0,), + units: Some(common::AreaUnits("m**2".to_string()),), + },), + drag_coeff: Some(common::NonNegativeDouble(2.3,)), + },), + covariance_matrix: None, + maneuver_parameters_list: vec![ + ManeuverParametersType { + comment_list: vec![], + man_epoch_ignition: common::EpochType( + "2021-06-03T09:00:34.1".to_string(), + ), + man_duration: common::DurationType { + base: common::NonNegativeDouble(132.6,), + units: Some(common::TimeUnits("s".to_string()),), + }, + man_delta_mass: common::DeltamassType { + base: common::NegativeDouble(-18.418,), + units: Some(common::MassUnits("kg".to_string()),), + }, + man_ref_frame: "EME2000".to_string(), + man_dv_1: common::VelocityType { + base: -0.023257, + units: Some(common::VelocityUnits("km/s".to_string()),), + }, + man_dv_2: common::VelocityType { + base: 0.0168316, + units: Some(common::VelocityUnits("km/s".to_string()),), + }, + man_dv_3: common::VelocityType { + base: -0.00893444, + units: Some(common::VelocityUnits("km/s".to_string()),), + }, + }, + ManeuverParametersType { + comment_list: vec![ + "Second maneuver: first station acquisition maneuver" + .to_string(), + "impulsive, thrust direction fixed in RTN frame" + .to_string(), + ], + man_epoch_ignition: common::EpochType( + "2021-06-05T18:59:21.0".to_string(), + ), + man_duration: common::DurationType { + base: common::NonNegativeDouble(0.0,), + units: Some(common::TimeUnits("s".to_string()),), + }, + man_delta_mass: common::DeltamassType { + base: common::NegativeDouble(-1.469,), + units: Some(common::MassUnits("kg".to_string()),), + }, + man_ref_frame: "RTN".to_string(), + man_dv_1: common::VelocityType { + base: 0.001015, + units: Some(common::VelocityUnits("km/s".to_string()),), + }, + man_dv_2: common::VelocityType { + base: -0.001873, + units: Some(common::VelocityUnits("km/s".to_string()),), + }, + man_dv_3: common::VelocityType { + base: 0.0, + units: Some(common::VelocityUnits("km/s".to_string()),), + }, + }, + ], + user_defined_parameters: None, + }, + }, + }, + id: None, + version: "3.0".to_string(), + },) + ); + } +} diff --git a/crates/lox-io/src/ndm/xml.rs b/crates/lox-io/src/ndm/xml.rs new file mode 100644 index 00000000..52dabd49 --- /dev/null +++ b/crates/lox-io/src/ndm/xml.rs @@ -0,0 +1,9 @@ +//! The public interface for the XML deserializer type + +pub(crate) mod error; + +pub use error::XmlDeserializationError; + +mod deserializer; + +pub use deserializer::FromXmlStr; diff --git a/crates/lox-io/src/ndm/xml/deserializer.rs b/crates/lox-io/src/ndm/xml/deserializer.rs new file mode 100644 index 00000000..919f0a2a --- /dev/null +++ b/crates/lox-io/src/ndm/xml/deserializer.rs @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2023. Helge Eichhorn and the LOX contributors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + */ + +/// Parse an NDM message from a string formatted in XML +pub trait FromXmlStr<'a>: Sized + serde::Deserialize<'a> { + fn from_xml_str(xml: &'a str) -> Result { + Ok(quick_xml::de::from_str(xml)?) + } +} diff --git a/crates/lox-io/src/ndm/xml/error.rs b/crates/lox-io/src/ndm/xml/error.rs new file mode 100644 index 00000000..0815ce4e --- /dev/null +++ b/crates/lox-io/src/ndm/xml/error.rs @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2023. Helge Eichhorn and the LOX contributors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + */ + +/// Deserialization error. Simple wrapper for +/// quick_xml::errors::serialize::DeError. Check the source class for further +/// documentation. +#[derive(Clone, Debug)] +pub enum XmlDeserializationError { + Custom(String), + InvalidXml(String), + InvalidInt(String), + InvalidFloat(String), + InvalidBoolean(String), + KeyNotRead(String), + UnexpectedStart(String), + UnexpectedEnd(String), + UnexpectedEof(String), + ExpectedStart(String), + Unsupported(String), +} + +impl std::fmt::Display for XmlDeserializationError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + XmlDeserializationError::Custom(s) => write!(f, "{}", s), + XmlDeserializationError::InvalidXml(s) => write!(f, "{}", s), + XmlDeserializationError::InvalidInt(s) => write!(f, "{}", s), + XmlDeserializationError::InvalidFloat(s) => write!(f, "{}", s), + XmlDeserializationError::InvalidBoolean(s) => write!(f, "{}", s), + XmlDeserializationError::KeyNotRead(s) => write!(f, "{}", s), + XmlDeserializationError::UnexpectedStart(s) => write!(f, "{}", s), + XmlDeserializationError::UnexpectedEnd(s) => write!(f, "{}", s), + XmlDeserializationError::UnexpectedEof(s) => write!(f, "{}", s), + XmlDeserializationError::ExpectedStart(s) => write!(f, "{}", s), + XmlDeserializationError::Unsupported(s) => write!(f, "{}", s), + } + } +} + +impl ::std::error::Error for XmlDeserializationError {} + +impl From for XmlDeserializationError { + fn from(value: quick_xml::DeError) -> Self { + let error_description = format!("{:?}", value).to_string(); + + match value { + quick_xml::DeError::Custom(_) => XmlDeserializationError::Custom(error_description), + quick_xml::DeError::InvalidXml(_) => { + XmlDeserializationError::InvalidXml(error_description) + } + quick_xml::DeError::InvalidInt(_) => { + XmlDeserializationError::InvalidInt(error_description) + } + quick_xml::DeError::InvalidFloat(_) => { + XmlDeserializationError::InvalidFloat(error_description) + } + quick_xml::DeError::InvalidBoolean(_) => { + XmlDeserializationError::InvalidBoolean(error_description) + } + quick_xml::DeError::KeyNotRead => { + XmlDeserializationError::KeyNotRead(error_description) + } + quick_xml::DeError::UnexpectedStart(_) => { + XmlDeserializationError::UnexpectedStart(error_description) + } + quick_xml::DeError::UnexpectedEnd(_) => { + XmlDeserializationError::UnexpectedEnd(error_description) + } + quick_xml::DeError::UnexpectedEof => { + XmlDeserializationError::UnexpectedEof(error_description) + } + quick_xml::DeError::ExpectedStart => { + XmlDeserializationError::ExpectedStart(error_description) + } + quick_xml::DeError::Unsupported(_) => { + XmlDeserializationError::Unsupported(error_description) + } + } + } +} diff --git a/crates/lox-time/src/subsecond.rs b/crates/lox-time/src/subsecond.rs index cd69dfa5..5843b44f 100644 --- a/crates/lox-time/src/subsecond.rs +++ b/crates/lox-time/src/subsecond.rs @@ -189,7 +189,8 @@ mod tests { #[test] fn test_subsecond_into_f64() { let subsecond = Subsecond(0.0); - assert_eq!(0.0, subsecond.into()); + let as_f64: f64 = subsecond.into(); + assert_eq!(0.0, as_f64); } #[test]