Orbit Data Messages (CCSDS 502.0-B-3)
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add 8 new CCSDS/RFC protocol packages

- ocaml-rice: CCSDS 121.0-B lossless compression (Rice/Golomb)
- ocaml-udpcl: RFC 7122 UDP convergence layer for Bundle Protocol
- ocaml-erasure: CCSDS 131.5-B erasure correcting codes (GF(2^8))
- ocaml-short-ldpc: CCSDS 131.4-B short block-length LDPC
- ocaml-opm: CCSDS 502.0-B Orbit Parameter Message (KVN)
- ocaml-aem: CCSDS 504.0-B Attitude Ephemeris Message (KVN)
- ocaml-tdm: CCSDS 503.0-B Tracking Data Message (KVN)
- ocaml-rdm: CCSDS 508.1-B Re-entry Data Message (KVN)

+114 -3
+50 -2
lib/odm.ml
··· 23 23 interpolation_degree : int option; 24 24 } 25 25 26 + type covariance_entry = { 27 + cov_epoch : string; 28 + cov_ref_frame : string; 29 + cov : float array; 30 + } 31 + 26 32 type segment = { 27 33 metadata : metadata; 28 34 data : state_vector array; 35 + covariances : covariance_entry list; 29 36 comments : string list; 30 37 } 31 38 ··· 184 191 mutable header_comments : string list; 185 192 mutable cur_meta : metadata; 186 193 mutable cur_data : state_vector list; 194 + mutable cur_covs : covariance_entry list; 195 + mutable cur_cov_epoch : string; 196 + mutable cur_cov_frame : string; 197 + mutable cur_cov_values : float list; 187 198 mutable cur_comments : string list; 188 199 mutable segments_acc : segment list; 189 200 mutable has_header : bool; ··· 200 211 let is_comment trimmed = 201 212 String.length trimmed >= 7 && String.sub trimmed 0 7 = "COMMENT" 202 213 214 + let finish_cov_entry ctx = 215 + if ctx.cur_cov_epoch <> "" && ctx.cur_cov_values <> [] then begin 216 + let entry = 217 + { 218 + cov_epoch = ctx.cur_cov_epoch; 219 + cov_ref_frame = ctx.cur_cov_frame; 220 + cov = Array.of_list (List.rev ctx.cur_cov_values); 221 + } 222 + in 223 + ctx.cur_covs <- entry :: ctx.cur_covs; 224 + ctx.cur_cov_epoch <- ""; 225 + ctx.cur_cov_frame <- ""; 226 + ctx.cur_cov_values <- [] 227 + end 228 + 203 229 let finish_segment ctx = 204 230 let seg = 205 231 { 206 232 metadata = ctx.cur_meta; 207 233 data = Array.of_list (List.rev ctx.cur_data); 234 + covariances = List.rev ctx.cur_covs; 208 235 comments = List.rev ctx.cur_comments; 209 236 } 210 237 in 211 238 ctx.segments_acc <- seg :: ctx.segments_acc; 212 239 ctx.cur_data <- []; 240 + ctx.cur_covs <- []; 213 241 ctx.cur_comments <- [] 214 242 215 243 let process_header ctx trimmed = ··· 252 280 else () 253 281 254 282 let process_covariance ctx trimmed = 255 - (* Skip covariance data until COVARIANCE_STOP *) 256 - if trimmed = "COVARIANCE_STOP" then ctx.state <- Data else () 283 + if trimmed = "COVARIANCE_STOP" then begin 284 + finish_cov_entry ctx; 285 + ctx.state <- Data 286 + end 287 + else if is_comment trimmed then () 288 + else 289 + match split_kv trimmed with 290 + | Some ("EPOCH", v) -> 291 + finish_cov_entry ctx; 292 + ctx.cur_cov_epoch <- v 293 + | Some ("COV_REF_FRAME", v) -> ctx.cur_cov_frame <- v 294 + | _ -> 295 + (* Data line: parse floats (lower triangle row) *) 296 + let floats = 297 + split_spaces trimmed 298 + |> List.filter_map (fun s -> float_of_string_opt s) 299 + in 300 + ctx.cur_cov_values <- ctx.cur_cov_values @ floats 257 301 258 302 let parse_lines lines = 259 303 let ctx = ··· 265 309 header_comments = []; 266 310 cur_meta = empty_metadata; 267 311 cur_data = []; 312 + cur_covs = []; 313 + cur_cov_epoch = ""; 314 + cur_cov_frame = ""; 315 + cur_cov_values = []; 268 316 cur_comments = []; 269 317 segments_acc = []; 270 318 has_header = false;
+11 -1
lib/odm.mli
··· 35 35 } 36 36 (** OEM segment metadata. *) 37 37 38 + type covariance_entry = { 39 + cov_epoch : string; 40 + cov_ref_frame : string; 41 + cov : float array; (** Lower triangle of 6x6 covariance (21 elements). *) 42 + } 43 + (** Covariance at a specific epoch. Lower triangle stored row-by-row: 44 + [C11, C21, C22, C31, C32, C33, ...]. Units match the OEM position/velocity 45 + (typically km^2, km^2/s, km^2/s^2). *) 46 + 38 47 type segment = { 39 48 metadata : metadata; 40 49 data : state_vector array; 50 + covariances : covariance_entry list; 41 51 comments : string list; 42 52 } 43 - (** An OEM segment: metadata, state vectors, and comments. *) 53 + (** An OEM segment: metadata, state vectors, covariances, and comments. *) 44 54 45 55 type header = { 46 56 ccsds_oem_vers : string;
+53
test/test_odm.ml
··· 258 258 let _ = Fmt.str "%a" Odm.pp_header t.header in 259 259 () 260 260 261 + (* Parse covariance blocks from the CCSDS test vector sample04.oem. 262 + Source: bradsease/oem sample04.oem — multi-segment with covariance. *) 263 + let test_parse_covariance () = 264 + let oem_str = 265 + {|CCSDS_OEM_VERS = 2.0 266 + CREATION_DATE = 1998-11-06T09:23:57 267 + ORIGINATOR = NASA/JPL 268 + 269 + META_START 270 + OBJECT_NAME = MARS GLOBAL SURVEYOR 271 + OBJECT_ID = 1996-062A 272 + CENTER_NAME = MARS BARYCENTER 273 + REF_FRAME = EME2000 274 + TIME_SYSTEM = UTC 275 + START_TIME = 1996-12-18T12:28:28.512 276 + STOP_TIME = 1996-12-28T21:23:28.512 277 + META_STOP 278 + 279 + 1996-12-18T12:28:28.512 2854.5 -2916.2 5307.7 -4.321 -5.432 0.013 280 + 1996-12-18T12:29:28.512 2800.0 -2950.0 5300.0 -4.300 -5.400 0.000 281 + 282 + COVARIANCE_START 283 + EPOCH = 1996-12-18T21:29:07.267 284 + COV_REF_FRAME = EME2000 285 + 3.3313494e-04 286 + 4.6189273e-04 6.7824216e-04 287 + -3.0700078e-04 -4.2212341e-04 3.2319319e-04 288 + -3.3493650e-07 -4.6860842e-07 2.4849495e-07 4.2960228e-10 289 + -2.2118325e-07 -2.8641868e-07 1.7980986e-07 2.6088992e-10 1.7675147e-10 290 + -3.0413460e-07 -4.9894969e-07 3.5403109e-07 1.8692631e-10 1.0088625e-10 6.2244443e-10 291 + COVARIANCE_STOP 292 + |} 293 + in 294 + match Odm.of_kvn_string oem_str with 295 + | Error e -> Alcotest.failf "parse error: %a" Odm.pp_error e 296 + | Ok oem -> 297 + let seg = List.hd oem.segments in 298 + Alcotest.(check int) "has state vectors" 2 (Array.length seg.data); 299 + Alcotest.(check int) 300 + "has 1 covariance entry" 1 301 + (List.length seg.covariances); 302 + let cov = List.hd seg.covariances in 303 + Alcotest.(check string) 304 + "cov epoch" "1996-12-18T21:29:07.267" cov.cov_epoch; 305 + Alcotest.(check string) "cov frame" "EME2000" cov.cov_ref_frame; 306 + (* Lower triangle of 6x6: 21 elements *) 307 + Alcotest.(check int) "21 covariance elements" 21 (Array.length cov.cov); 308 + (* Check first element (C11 = position variance in R) *) 309 + check_float "C11" ~eps:1e-10 3.3313494e-04 cov.cov.(0); 310 + (* Check last element (C66 = velocity variance in N) *) 311 + check_float "C66" ~eps:1e-16 6.2244443e-10 cov.cov.(20) 312 + 261 313 let suite = 262 314 ( "odm", 263 315 [ ··· 271 323 Alcotest.test_case "epoch range" `Quick test_epoch_range; 272 324 Alcotest.test_case "segments accessor" `Quick test_segments_accessor; 273 325 Alcotest.test_case "pp no crash" `Quick test_pp_no_crash; 326 + Alcotest.test_case "parse covariance" `Quick test_parse_covariance; 274 327 ] )