@@ -218,3 +218,267 @@ def read_kazr(
218
218
elevation ,
219
219
instrument_parameters = instrument_parameters ,
220
220
)
221
+
222
+
223
+ def read_mmcr (
224
+ filename ,
225
+ field_names = None ,
226
+ additional_metadata = None ,
227
+ file_field_names = False ,
228
+ exclude_fields = None ,
229
+ include_fields = None ,
230
+ mode_names = None ,
231
+ mode_to_extract = None ,
232
+ ):
233
+ """
234
+ Read millimeter wavelength cloud radar (MMCR) b1 NetCDF ingest data.
235
+
236
+ Parameters
237
+ ----------
238
+ filename : str
239
+ Name of NetCDF file to read data from.
240
+ field_names : dict, optional
241
+ Dictionary mapping field names in the file names to radar field names.
242
+ Unlike other read functions, fields not in this dictionary or having a
243
+ value of None are still included in the radar.fields dictionary, to
244
+ exclude them use the `exclude_fields` parameter. Fields which are
245
+ mapped by this dictionary will be renamed from key to value.
246
+ additional_metadata : dict of dicts, optional
247
+ This parameter is not used, it is included for uniformity.
248
+ file_field_names : bool, optional
249
+ True to force the use of the field names from the file in which
250
+ case the `field_names` parameter is ignored. False will use to
251
+ `field_names` parameter to rename fields.
252
+ exclude_fields : list or None, optional
253
+ List of fields to exclude from the radar object. This is applied
254
+ after the `file_field_names` and `field_names` parameters. Set
255
+ to None to include all fields specified by include_fields.
256
+ include_fields : list or None, optional
257
+ List of fields to include from the radar object. This is applied
258
+ after the `file_field_names` and `field_names` parameters. Set
259
+ to None to include all fields not specified by exclude_fields.
260
+ mode_names: dict
261
+ Keys are mode dimension indices. Values are the corresponding mode
262
+ abbreviations and prt. Using default values if None.
263
+ mode_to_extract: str or None
264
+ Abbreviation of mode to extract.
265
+ Extracting all data for all modes if None.
266
+
267
+ Returns
268
+ -------
269
+ radar_out : Radar or dict of Radar objects
270
+ If `mode_to_extract` is None, returns a dict with keys representing operated modes and
271
+ values the corresponding `Radar` object.
272
+ If `mode_to_extract` is specified returning the `Radar` object corresponding to the that
273
+ mode.
274
+
275
+ """
276
+
277
+ if mode_names is None :
278
+ mode_names = {
279
+ 1 : "bl" ,
280
+ 2 : "ci" ,
281
+ 3 : "ge" ,
282
+ 4 : "pr" ,
283
+ 5 : "dualpol_ch0" , # Dual-pol receiver 0
284
+ 6 : "dualpol_ch1" , # Dual-pol receiver 1
285
+ }
286
+
287
+ # create metadata retrieval object
288
+ filemetadata = FileMetadata (
289
+ "cfradial" ,
290
+ field_names ,
291
+ additional_metadata ,
292
+ file_field_names ,
293
+ exclude_fields ,
294
+ include_fields ,
295
+ )
296
+
297
+ # read the data
298
+ ncobj = netCDF4 .Dataset (filename )
299
+ ncvars = ncobj .variables
300
+
301
+ # 4.1 Global attribute -> move to metadata dictionary
302
+ metadata = {k : getattr (ncobj , k ) for k in ncobj .ncattrs ()}
303
+ metadata ["n_gates_vary" ] = "false"
304
+
305
+ # 4.2 Dimensions (do nothing)
306
+
307
+ # 4.3 Global variable -> move to metadata dictionary
308
+ if "volume_number" in ncvars :
309
+ metadata ["volume_number" ] = int (ncvars ["volume_number" ][:])
310
+ else :
311
+ metadata ["volume_number" ] = 0
312
+
313
+ global_vars = {
314
+ "platform_type" : "fixed" ,
315
+ "instrument_type" : "radar" ,
316
+ "primary_axis" : "axis_z" ,
317
+ }
318
+ # ignore time_* global variables, these are calculated from the time
319
+ # variable when the file is written.
320
+ for var , default_value in global_vars .items ():
321
+ if var in ncvars :
322
+ metadata [var ] = str (netCDF4 .chartostring (ncvars [var ][:]))
323
+ else :
324
+ metadata [var ] = default_value
325
+
326
+ # 4.4 coordinate variables -> create attribute dictionaries (`_range` is generated in loop below)
327
+
328
+ # 4.5 Ray dimension variables
329
+
330
+ # 4.6 Location variables -> create attribute dictionaries
331
+ # the only difference in this section to cfradial.read_cfradial is the
332
+ # minor variable name differences:
333
+ # latitude -> lat
334
+ # longitude -> lon
335
+ # altitdue -> alt
336
+ latitude = cfradial ._ncvar_to_dict (ncvars ["lat" ])
337
+ longitude = cfradial ._ncvar_to_dict (ncvars ["lon" ])
338
+ altitude = cfradial ._ncvar_to_dict (ncvars ["alt" ])
339
+
340
+ # 4.7 Sweep variables -> create atrribute dictionaries
341
+ # this is the section that needed the most work since the initial NetCDF
342
+ # file did not contain any sweep information
343
+ sweep_number = filemetadata ("sweep_number" )
344
+ sweep_number ["data" ] = np .array ([0 ], dtype = np .int32 )
345
+
346
+ sweep_mode = filemetadata ("sweep_mode" )
347
+ sweep_mode ["data" ] = np .array (["vertical_pointing" ], dtype = str )
348
+
349
+ fixed_angle = filemetadata ("fixed_angle" )
350
+ fixed_angle ["data" ] = np .array ([90.0 ], dtype = np .float32 )
351
+
352
+ sweep_start_ray_index = filemetadata ("sweep_start_ray_index" )
353
+ sweep_start_ray_index ["data" ] = np .array ([0 ], dtype = np .int32 )
354
+
355
+ # first sweep mode determines scan_type
356
+ # this module is specific to vertically-pointing data
357
+ scan_type = "vpt"
358
+
359
+ # 4.8 Sensor pointing variables -> create attribute dictionaries
360
+ # this section also required some changes since the initial NetCDF did not
361
+ # contain any sensor pointing variables
362
+
363
+ # 4.9 Moving platform geo-reference variables
364
+
365
+ # 4.10 Moments field data variables -> field attribute dictionary
366
+ # all variables with dimensions of 'time', 'heights' are fields
367
+ keys = [k for k , v in ncvars .items () if v .dimensions == ("time" , "heights" )]
368
+
369
+ # Determine if one or more modes are to be extracted and generate Radar object(s) output
370
+ if mode_to_extract is None :
371
+ modes = np .unique (ncvars ["ModeNum" ])
372
+ radar_out = {}
373
+ else :
374
+ modes = [key for key in mode_names .keys () if mode_names [key ] == mode_to_extract ]
375
+ for mode in modes :
376
+ mode_sample_indices = ncvars ["ModeNum" ] == np .array (mode )
377
+ active_range = ncvars ["heights" ][mode , :] > 0.0
378
+ range_arr = ncvars ["heights" ][mode , active_range ] - altitude ["data" ]
379
+ time = cfradial ._ncvar_to_dict (ncvars ["time" ])
380
+ time ["data" ] = ncvars ["time" ][mode_sample_indices ]
381
+ _range = {
382
+ "long_name" : "Range (center of radar sample volume)" ,
383
+ "units" : "m AGL" ,
384
+ "missing_value" : - 9999.0 ,
385
+ "data" : range_arr ,
386
+ }
387
+
388
+ fields = {}
389
+ for key in keys :
390
+ field_name = filemetadata .get_field_name (key )
391
+ if field_name is None :
392
+ if exclude_fields is not None and key in exclude_fields :
393
+ continue
394
+ if include_fields is not None and key not in include_fields :
395
+ continue
396
+ field_name = key
397
+
398
+ d = {
399
+ k : getattr (ncvars [key ], k )
400
+ for k in ncvars [key ].ncattrs ()
401
+ if k not in ["scale_factor" , "add_offset" ]
402
+ }
403
+ d ["data" ] = ncvars [key ][mode_sample_indices , active_range ]
404
+ fields [field_name ] = d
405
+
406
+ # 4.5 instrument_parameters sub-convention -> instrument_parameters dict
407
+ # this section needed multiple changes and/or additions since the
408
+ # instrument parameters were primarily located in the global attributes
409
+ # this section is likely still incomplete
410
+
411
+ sweep_end_ray_index = filemetadata ("sweep_end_ray_index" )
412
+ sweep_end_ray_index ["data" ] = np .array ([time ["data" ].size - 1 ], dtype = np .int32 )
413
+
414
+ azimuth = filemetadata ("azimuth" )
415
+ azimuth ["data" ] = 0.0 * np .ones (time ["data" ].size , dtype = np .float32 )
416
+
417
+ elevation = filemetadata ("elevation" )
418
+ elevation ["data" ] = 90.0 * np .ones (time ["data" ].size , dtype = np .float32 )
419
+
420
+ omega = float (ncobj .radar_operating_frequency .split ()[0 ])
421
+ frequency = filemetadata ("frequency" )
422
+ frequency ["data" ] = np .array ([omega / 1e9 ], dtype = np .float32 )
423
+
424
+ prt_mode = filemetadata ("prt_mode" )
425
+ prt_mode ["data" ] = np .array (["fixed" ], dtype = str )
426
+
427
+ prt = filemetadata ("prt" )
428
+ prt ["data" ] = np .full (
429
+ time ["data" ].size , ncvars ["InterPulsePeriod" ][mode ] * 1e-9 , dtype = np .float32
430
+ )
431
+
432
+ nyquist_velocity = filemetadata ("nyquist_velocity" )
433
+ nyquist_velocity ["data" ] = np .full (
434
+ time ["data" ].size , ncvars ["NyquistVelocity" ][mode ], dtype = np .float32
435
+ )
436
+
437
+ n_samples = filemetadata ("n_samples" )
438
+ n_samples ["data" ] = np .full (
439
+ time ["data" ].size , ncvars ["NumSpectralAverages" ][mode ], dtype = np .float32
440
+ )
441
+
442
+ # 4.6 radar_parameters sub-convention -> instrument_parameters dict
443
+ # this section needed multiple changes and/or additions since the
444
+ # radar instrument parameters were primarily located in the global
445
+ # attributes
446
+ # this section is likely still incomplete
447
+ instrument_parameters = {
448
+ "frequency" : frequency ,
449
+ "prt_mode" : prt_mode ,
450
+ "prt" : prt ,
451
+ "nyquist_velocity" : nyquist_velocity ,
452
+ "n_samples" : n_samples ,
453
+ }
454
+
455
+ # 4.7 lidar_parameters sub-convention -> skip
456
+ # 4.8 radar_calibration sub-convention -> skip
457
+
458
+ Radar_tmp = Radar (
459
+ time ,
460
+ _range ,
461
+ fields ,
462
+ metadata ,
463
+ scan_type ,
464
+ latitude ,
465
+ longitude ,
466
+ altitude ,
467
+ sweep_number ,
468
+ sweep_mode ,
469
+ fixed_angle ,
470
+ sweep_start_ray_index ,
471
+ sweep_end_ray_index ,
472
+ azimuth ,
473
+ elevation ,
474
+ instrument_parameters = instrument_parameters ,
475
+ )
476
+ if mode_to_extract is None :
477
+ radar_out [mode_names [mode ]] = Radar_tmp
478
+ else :
479
+ radar_out = Radar_tmp
480
+
481
+ # close NetCDF object
482
+ ncobj .close ()
483
+
484
+ return radar_out
0 commit comments