@@ -20,10 +20,11 @@ use crate::io::watermark::prj_name_ver;
20
20
use crate :: io:: ExportCfg ;
21
21
use crate :: io:: { InputOutputError , StdIOSnafu } ;
22
22
use crate :: od:: msr:: { Measurement , MeasurementType } ;
23
+ use anise:: constants:: SPEED_OF_LIGHT_KM_S ;
23
24
use hifitime:: efmt:: { Format , Formatter } ;
24
25
use hifitime:: prelude:: Epoch ;
25
26
use hifitime:: TimeScale ;
26
- use indexmap:: IndexMap ;
27
+ use indexmap:: { IndexMap , IndexSet } ;
27
28
use snafu:: ResultExt ;
28
29
use std:: collections:: { BTreeMap , HashMap } ;
29
30
use std:: fs:: File ;
@@ -44,13 +45,18 @@ impl TrackingDataArc {
44
45
action : "opening CCSDS TDM file for tracking arc" ,
45
46
} ) ?;
46
47
48
+ let source = path. as_ref ( ) . to_path_buf ( ) . display ( ) . to_string ( ) ;
49
+ info ! ( "parsing CCSDS TDM {source}" ) ;
50
+
47
51
let mut measurements = BTreeMap :: new ( ) ;
52
+ let mut metadata = HashMap :: new ( ) ;
48
53
49
54
let reader = BufReader :: new ( file) ;
50
55
51
56
let mut in_data_section = false ;
52
57
let mut current_tracker = String :: new ( ) ;
53
58
let mut time_system = TimeScale :: UTC ;
59
+ let mut has_freq_data = false ;
54
60
55
61
for line in reader. lines ( ) {
56
62
let line = line. context ( StdIOSnafu {
@@ -88,10 +94,27 @@ impl TrackingDataArc {
88
94
}
89
95
}
90
96
}
97
+
98
+ let mut splt = line. split ( '=' ) ;
99
+ if let Some ( keyword) = splt. nth ( 0 ) {
100
+ // Get the zeroth item again since we've consumed the first zeroth one.
101
+ if let Some ( value) = splt. nth ( 0 ) {
102
+ metadata. insert ( keyword. trim ( ) . to_string ( ) , value. trim ( ) . to_string ( ) ) ;
103
+ }
104
+ }
105
+
91
106
continue ;
92
107
}
93
108
94
109
if let Some ( ( mtype, epoch, value) ) = parse_measurement_line ( line, time_system) ? {
110
+ if [
111
+ MeasurementType :: ReceiveFrequency ,
112
+ MeasurementType :: TransmitFrequency ,
113
+ ]
114
+ . contains ( & mtype)
115
+ {
116
+ has_freq_data = true ;
117
+ }
95
118
measurements
96
119
. entry ( epoch)
97
120
. or_insert_with ( || Measurement {
@@ -104,10 +127,127 @@ impl TrackingDataArc {
104
127
}
105
128
}
106
129
107
- Ok ( Self {
130
+ let mut turnaround_ratio = None ;
131
+ let drop_freq_data;
132
+ if has_freq_data {
133
+ // If there is any frequency measurement, compute the turn-around ratio.
134
+ if let Some ( ta_num_str) = metadata. get ( "TURNAROUND_NUMERATOR" ) {
135
+ if let Some ( ta_denom_str) = metadata. get ( "TURNAROUND_DENOMINATOR" ) {
136
+ if let Ok ( ta_num) = ta_num_str. parse :: < i32 > ( ) {
137
+ if let Ok ( ta_denom) = ta_denom_str. parse :: < i32 > ( ) {
138
+ // turn-around ratio is set.
139
+ turnaround_ratio = Some ( f64:: from ( ta_num) / f64:: from ( ta_denom) ) ;
140
+ info ! ( "turn-around ratio is {ta_num}/{ta_denom}" ) ;
141
+ drop_freq_data = false ;
142
+ } else {
143
+ error ! ( "turn-around denominator `{ta_denom_str}` is not a valid double precision float" ) ;
144
+ drop_freq_data = true ;
145
+ }
146
+ } else {
147
+ error ! ( "turn-around numerator `{ta_num_str}` is not a valid double precision float" ) ;
148
+ drop_freq_data = true ;
149
+ }
150
+ } else {
151
+ error ! ( "required turn-around denominator missing from metadata -- dropping ALL RECEIVE/TRANSMIT data" ) ;
152
+ drop_freq_data = true ;
153
+ }
154
+ } else {
155
+ error ! ( "required turn-around numerator missing from metadata -- dropping ALL RECEIVE/TRANSMIT data" ) ;
156
+ drop_freq_data = true ;
157
+ }
158
+ } else {
159
+ drop_freq_data = true ;
160
+ }
161
+
162
+ // Now, let's convert the receive and transmit frequencies to Doppler measurements in velocity units.
163
+ // We expect the transmit and receive frequencies to have the exact same timestamp.
164
+ let mut freq_types = IndexSet :: new ( ) ;
165
+ freq_types. insert ( MeasurementType :: ReceiveFrequency ) ;
166
+ freq_types. insert ( MeasurementType :: TransmitFrequency ) ;
167
+ let mut latest_transmit_freq = None ;
168
+ for ( epoch, measurement) in measurements. iter_mut ( ) {
169
+ if drop_freq_data {
170
+ for freq in & freq_types {
171
+ measurement. data . swap_remove ( freq) ;
172
+ }
173
+ continue ;
174
+ }
175
+
176
+ let avail = measurement. availability ( & freq_types) ;
177
+ let use_prev_transmit_freq;
178
+ let num_freq_msr = avail
179
+ . iter ( )
180
+ . copied ( )
181
+ . map ( |v| if v { 1 } else { 0 } )
182
+ . sum :: < u8 > ( ) ;
183
+ if num_freq_msr == 0 {
184
+ // No frequency measurements
185
+ continue ;
186
+ } else if num_freq_msr == 1 {
187
+ // avail[0] means that Receive Freq is available
188
+ // avail[1] means that Transmit Freq is available
189
+ // We can only compute Doppler data from one data point if that data point
190
+ // if the receive frequency and the transmit frequency was previously set.
191
+ if latest_transmit_freq. is_some ( ) && avail[ 0 ] {
192
+ use_prev_transmit_freq = true ;
193
+ warn ! (
194
+ "no transmit frequency at {epoch}, using previous value of {} Hz" ,
195
+ latest_transmit_freq. unwrap( )
196
+ ) ;
197
+ } else {
198
+ warn ! ( "only one of receive or transmit frequencies found at {epoch}, ignoring" ) ;
199
+ for freq in & freq_types {
200
+ measurement. data . swap_remove ( freq) ;
201
+ }
202
+ continue ;
203
+ }
204
+ } else {
205
+ use_prev_transmit_freq = false ;
206
+ }
207
+
208
+ if !use_prev_transmit_freq {
209
+ // Update the latest transmit frequency since it's set.
210
+ latest_transmit_freq = Some (
211
+ * measurement
212
+ . data
213
+ . get ( & MeasurementType :: TransmitFrequency )
214
+ . unwrap ( ) ,
215
+ ) ;
216
+ }
217
+
218
+ let transmit_freq_hz = latest_transmit_freq. unwrap ( ) ;
219
+ let receive_freq_hz = * measurement
220
+ . data
221
+ . get ( & MeasurementType :: ReceiveFrequency )
222
+ . unwrap ( ) ;
223
+
224
+ // Compute the Doppler shift, equation from section 3.5.2.8.2 of CCSDS TDM v2 specs
225
+ let doppler_shift_hz = transmit_freq_hz * turnaround_ratio. unwrap ( ) - receive_freq_hz;
226
+ // Compute the expected Doppler measurement as range-rate.
227
+ let rho_dot_km_s = ( doppler_shift_hz * SPEED_OF_LIGHT_KM_S )
228
+ / ( 2.0 * transmit_freq_hz * turnaround_ratio. unwrap ( ) ) ;
229
+
230
+ // Finally, replace the frequency data with a Doppler measurement.
231
+ for freq in & freq_types {
232
+ measurement. data . swap_remove ( freq) ;
233
+ }
234
+ measurement
235
+ . data
236
+ . insert ( MeasurementType :: Doppler , rho_dot_km_s) ;
237
+ }
238
+
239
+ let trk = Self {
108
240
measurements,
109
- source : Some ( path. as_ref ( ) . to_path_buf ( ) . display ( ) . to_string ( ) ) ,
110
- } )
241
+ source : Some ( source) ,
242
+ } ;
243
+
244
+ if trk. unique_types ( ) . is_empty ( ) {
245
+ Err ( InputOutputError :: EmptyDataset {
246
+ action : "CCSDS TDM file" ,
247
+ } )
248
+ } else {
249
+ Ok ( trk)
250
+ }
111
251
}
112
252
113
253
/// Store this tracking arc to a CCSDS TDM file, with optional metadata and a timestamp appended to the filename.
@@ -254,6 +394,8 @@ impl TrackingDataArc {
254
394
MeasurementType :: Doppler => "DOPPLER_INTEGRATED" ,
255
395
MeasurementType :: Azimuth => "ANGLE_1" ,
256
396
MeasurementType :: Elevation => "ANGLE_2" ,
397
+ MeasurementType :: ReceiveFrequency => "RECEIVE_FREQ" ,
398
+ MeasurementType :: TransmitFrequency => "TRANSMIT_FREQ" ,
257
399
} ;
258
400
259
401
writeln ! (
@@ -295,6 +437,10 @@ fn parse_measurement_line(
295
437
"DOPPLER_INSTANTANEOUS" | "DOPPLER_INTEGRATED" => MeasurementType :: Doppler ,
296
438
"ANGLE_1" => MeasurementType :: Azimuth ,
297
439
"ANGLE_2" => MeasurementType :: Elevation ,
440
+ "RECEIVE_FREQ" | "RECEIVE_FREQ_1" | "RECEIVE_FREQ_2" | "RECEIVE_FREQ_3"
441
+ | "RECEIVE_FREQ_4" | "RECEIVE_FREQ_5" => MeasurementType :: ReceiveFrequency ,
442
+ "TRANSMIT_FREQ" | "TRANSMIT_FREQ_1" | "TRANSMIT_FREQ_2" | "TRANSMIT_FREQ_3"
443
+ | "TRANSMIT_FREQ_4" | "TRANSMIT_FREQ_5" => MeasurementType :: TransmitFrequency ,
298
444
_ => {
299
445
return Err ( InputOutputError :: UnsupportedData {
300
446
which : mtype_str. to_string ( ) ,
0 commit comments