metradar 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. metradar/__init__.py +7 -0
  2. metradar/cnrad_level2.py +1326 -0
  3. metradar/comm_func.py +135 -0
  4. metradar/construct_aws_refvpr_mainprog.py +515 -0
  5. metradar/construct_aws_refvpr_mainprog_cams.py +310 -0
  6. metradar/construct_aws_refvpr_mainprog_datan3d.py +386 -0
  7. metradar/construct_aws_refvpr_mainprog_swan.py +306 -0
  8. metradar/decode_fmt_pyart.py +200 -0
  9. metradar/decode_pup_rose.py +1993 -0
  10. metradar/draw_mosaic_new.py +421 -0
  11. metradar/draw_radar_aws_jilin_new.py +206 -0
  12. metradar/draw_radar_comp_func.py +1379 -0
  13. metradar/exceptions.py +50 -0
  14. metradar/geo_transforms_pyart.py +627 -0
  15. metradar/get_cross_section_from_pyart.py +354 -0
  16. metradar/get_tlogp_from_sharppy.py +93 -0
  17. metradar/grid.py +281 -0
  18. metradar/grid_data.py +64 -0
  19. metradar/main_pydda.py +653 -0
  20. metradar/make_gif.py +24 -0
  21. metradar/make_mosaic_mp_archive.py +538 -0
  22. metradar/mosaic_merge.py +64 -0
  23. metradar/mosaic_quickdraw.py +338 -0
  24. metradar/nowcast_by_pysteps.py +219 -0
  25. metradar/oa_couhua.py +166 -0
  26. metradar/oa_dig_func.py +955 -0
  27. metradar/parse_pal.py +148 -0
  28. metradar/pgmb_io.py +169 -0
  29. metradar/prepare_for_radar_draw.py +197 -0
  30. metradar/read_new_mosaic.py +33 -0
  31. metradar/read_new_mosaic_func.py +231 -0
  32. metradar/retrieve_cmadaas.py +3126 -0
  33. metradar/retrieve_micaps_server.py +2061 -0
  34. metradar/rose_structer.py +807 -0
  35. metradar/trans_nc_pgmb.py +62 -0
  36. metradar/trans_new_mosaic_nc.py +309 -0
  37. metradar/trans_polor2grid_func.py +203 -0
  38. metradar-0.1.0.dist-info/METADATA +12 -0
  39. metradar-0.1.0.dist-info/RECORD +41 -0
  40. metradar-0.1.0.dist-info/WHEEL +5 -0
  41. metradar-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1326 @@
1
+ """
2
+ Functions for reading CINRAD level 2 files.
3
+ some functions are based on pyart
4
+ https://github.com/ARM-DOE/pyart
5
+ by Wenjian Zhu
6
+
7
+ """
8
+
9
+
10
+ import bz2
11
+ from datetime import datetime, timedelta
12
+ import struct
13
+ import warnings
14
+ import re
15
+ import numpy as np
16
+
17
+
18
+ class CNRADLevel2File(object):
19
+ """
20
+ Class for accessing data in a CINRAD Level II file.
21
+ format file
22
+
23
+ Parameters
24
+ ----------
25
+ filename : str
26
+ Filename of Archive II file to read.
27
+
28
+ Attributes
29
+ ----------
30
+ radial_records : list
31
+ Radial (1 or 31) messages in the file.
32
+ nscans : int
33
+ Number of scans in the file.
34
+ scan_msgs : list of arrays
35
+ Each element specifies the indices of the message in the
36
+ radial_records attribute which belong to a given scan.
37
+ volume_header : dict
38
+ Volume header.
39
+ vcp : dict
40
+ VCP information dictionary.
41
+ _records : list
42
+ A list of all records (message) in the file.
43
+ _fh : file-like
44
+ File like object from which data is read.
45
+ _msg_type : '31' or '1':
46
+ Type of radial messages in file.
47
+
48
+
49
+ """
50
+
51
+ def __init__(self, filename):
52
+ """ initalize the object. """
53
+ # read in the volume header and compression_record
54
+
55
+ if hasattr(filename, 'read'):
56
+ fh = filename
57
+ else:
58
+ fh = open(filename, 'rb')
59
+ buf = fh.read()
60
+ self._fh = fh
61
+ pos = 0
62
+ # 获取通用信息头
63
+ dic_gh = _unpack_from_buf(buf, pos, GENERIC_HEADER)
64
+ if dic_gh['magic_number'] != 0x4D545352:
65
+ print('源数据格式错误!')
66
+ return
67
+ pos += _structure_size(GENERIC_HEADER)
68
+
69
+ # 读取站点配置
70
+ self.dic_stcfg = _unpack_from_buf(buf, pos, SITE_CONFIG)
71
+ pos += _structure_size(SITE_CONFIG)
72
+
73
+ self.radname = self.dic_stcfg['site_code'].decode('latin-1')[0:5]
74
+
75
+ # 读取任务配置
76
+ dic_tcfg = _unpack_from_buf(buf, pos, TASK_CONFIG)
77
+ pos += _structure_size(TASK_CONFIG)
78
+
79
+ # 获取扫描信息
80
+ self.cutinfo = []
81
+ for im in np.arange(dic_tcfg['cut_number']):
82
+ dic_cutcfg = _unpack_from_buf(buf, pos, SCAN_CONFIG)
83
+ self.cutinfo.append(dic_cutcfg)
84
+ pos = pos + _structure_size(SCAN_CONFIG)
85
+
86
+ csstr = dic_tcfg['task_name'].decode('latin-1')
87
+ task_name = csstr[0:csstr.find('\x00')]
88
+ self.vcp_type_num = [int(s) for s in re.findall(r'-?\d+\.?\d*', task_name)][0]
89
+
90
+ # read the records from the buffer
91
+ self._records = []
92
+ buf_length = len(buf)
93
+
94
+ while pos < buf_length:
95
+ pos, dic = self._get_record_from_buf(buf, pos)
96
+ self._records.append(dic)
97
+
98
+
99
+ elev_nums = np.array([m['msg_header']['elevation_number']
100
+ for m in self._records])
101
+ self.scan_msgs = [np.where(elev_nums == i + 1)[0]
102
+ for i in range(elev_nums.max())]
103
+ self.nscans = len(self.scan_msgs)
104
+
105
+ self._msg_type = '31'
106
+
107
+ outdic_vh = dict(zip([i[0] for i in VOLUME_HEADER],
108
+ [k for k in np.zeros(len(VOLUME_HEADER))]))
109
+ outdic_vh['tape'] = b'AR2V0006.'
110
+ outdic_vh['extension'] = b'001'
111
+
112
+ # 改为北京时间
113
+ # if time_type == 'BJT':
114
+ # dic_tcfg['scan_stime'] = dic_tcfg['scan_stime'] + 8*3600
115
+
116
+
117
+ outdic_vh['date'] = int(dic_tcfg['scan_stime'] / 86400) # 一天有86400秒
118
+ outdic_vh['time'] = 1000 * (dic_tcfg['scan_stime'] - outdic_vh['date'] * 86400)
119
+ outdic_vh['date'] = outdic_vh['date'] + 1
120
+ outdic_vh['icao'] = self.radname.encode()
121
+
122
+ self.volume_header = outdic_vh.copy()
123
+
124
+ # 构建VCP信息,也就是MSG5
125
+
126
+ msg_header = MSG_HEADER
127
+ dic_msg_header = dict(zip([i[0] for i in MSG_HEADER],
128
+ [k for k in np.zeros(len(MSG_HEADER))]))
129
+
130
+ dic_msg5_header = dict(zip([i[0] for i in MSG_5],
131
+ [k for k in np.zeros(len(MSG_5))]))
132
+
133
+ dic_msg5_elev = dict(zip([i[0] for i in MSG_5_ELEV],
134
+ [k for k in np.zeros(len(MSG_5_ELEV))]))
135
+
136
+ msg_header_size = _structure_size(MSG_HEADER)
137
+ msg5_header_size = _structure_size(MSG_5)
138
+ msg5_elev_size = _structure_size(MSG_5_ELEV)
139
+
140
+ dic_msg_header['size'] = (msg5_elev_size*dic_tcfg['cut_number'] + msg5_header_size + msg_header_size)//2
141
+ dic_msg_header['channels'] = 8
142
+ dic_msg_header['type'] = 5
143
+ dic_msg_header['seq_id'] = 1
144
+
145
+ date_t = int(dic_tcfg['scan_stime'] / 86400) # 一天有86400秒
146
+ time_t = 1000 * (dic_tcfg['scan_stime'] - date_t * 86400)
147
+ date_t = date_t + 1
148
+
149
+ dic_msg_header['date'] = date_t
150
+ dic_msg_header['ms'] = time_t
151
+ dic_msg_header['segments'] = 1
152
+ dic_msg_header['seg_num'] = 1
153
+
154
+ dic_msg5_header['msg_size'] = (msg5_elev_size*dic_tcfg['cut_number'] + msg5_header_size)//2
155
+ dic_msg5_header['pattern_type'] = 2
156
+
157
+
158
+ dic_msg5_header['pattern_number'] = self.vcp_type_num
159
+ dic_msg5_header['num_cuts'] = dic_tcfg['cut_number']
160
+ dic_msg5_header['clutter_map_group'] = 257
161
+ dic_msg5_header['doppler_vel_res'] = 2
162
+ dic_msg5_header['pulse_width'] = 2
163
+ dic_msg5_header['spare'] = b'0000000000'
164
+
165
+ # 添加 MSG_5_ELEV 仰角信息
166
+ cut_param=[]
167
+ for nn in range(dic_tcfg['cut_number']):
168
+
169
+ dic_msg5_elev['elevation_angle'] = int(self.cutinfo[nn]['elevation'] * 65536/360)
170
+ # print(cutinfo[nn]['elevation'])
171
+ dic_msg5_elev['channel_config'] = 0
172
+ if nn==0 or nn==2:
173
+ dic_msg5_elev['waveform_type'] = 1
174
+ dic_msg5_elev['super_resolution'] = 11
175
+ dic_msg5_elev['prf_number'] = 1
176
+ dic_msg5_elev['prf_pulse_count'] = 28
177
+ dic_msg5_elev['azimuth_rate'] = int(self.cutinfo[nn]['scan_speed'])
178
+ dic_msg5_elev['ref_thresh'] = 16
179
+ dic_msg5_elev['vel_thresh'] = 16
180
+ dic_msg5_elev['sw_thresh'] = 16
181
+ dic_msg5_elev['zdr_thres'] = 16
182
+ dic_msg5_elev['phi_thres'] = 16
183
+ dic_msg5_elev['rho_thres'] = 16
184
+ dic_msg5_elev['edge_angle_1'] = 0
185
+ dic_msg5_elev['dop_prf_num_1'] = 0
186
+ dic_msg5_elev['dop_prf_pulse_count_1'] = 0
187
+ dic_msg5_elev['spare_1'] = b'00'
188
+ dic_msg5_elev['edge_angle_2'] = 0
189
+ dic_msg5_elev['dop_prf_num_2'] = 0
190
+ dic_msg5_elev['dop_prf_pulse_count_2'] = 0
191
+ dic_msg5_elev['spare_2'] = b'00'
192
+ dic_msg5_elev['edge_angle_3'] = 0
193
+ dic_msg5_elev['dop_prf_num_3'] = 0
194
+ dic_msg5_elev['dop_prf_pulse_count_3'] = 0
195
+ dic_msg5_elev['spare_3'] = b'00'
196
+ elif nn==1 or nn==3:
197
+ dic_msg5_elev['waveform_type'] = 2
198
+ dic_msg5_elev['super_resolution'] = 7
199
+ dic_msg5_elev['prf_number'] = 0
200
+ dic_msg5_elev['prf_pulse_count'] = 0
201
+ dic_msg5_elev['azimuth_rate'] = int(self.cutinfo[nn]['scan_speed'])
202
+ dic_msg5_elev['ref_thresh'] = 16
203
+ dic_msg5_elev['vel_thresh'] = 16
204
+ dic_msg5_elev['sw_thresh'] = 16
205
+ dic_msg5_elev['zdr_thres'] = 16
206
+ dic_msg5_elev['phi_thres'] = 16
207
+ dic_msg5_elev['rho_thres'] = 16
208
+ dic_msg5_elev['edge_angle_1'] = 5464
209
+ dic_msg5_elev['dop_prf_num_1'] = 4
210
+ dic_msg5_elev['dop_prf_pulse_count_1'] = 75
211
+ dic_msg5_elev['spare_1'] = b'00'
212
+ dic_msg5_elev['edge_angle_2'] = 38232
213
+ dic_msg5_elev['dop_prf_num_2'] = 4
214
+ dic_msg5_elev['dop_prf_pulse_count_2'] = 75
215
+ dic_msg5_elev['spare_2'] = b'00'
216
+ dic_msg5_elev['edge_angle_3'] = 60984
217
+ dic_msg5_elev['dop_prf_num_3'] = 4
218
+ dic_msg5_elev['dop_prf_pulse_count_3'] = 75
219
+ dic_msg5_elev['spare_3'] = b'00'
220
+ else:
221
+ dic_msg5_elev['waveform_type'] = 4
222
+ dic_msg5_elev['super_resolution'] = 14
223
+ dic_msg5_elev['prf_number'] = 2
224
+ dic_msg5_elev['prf_pulse_count'] = 8
225
+ dic_msg5_elev['azimuth_rate'] = int(self.cutinfo[nn]['scan_speed'])
226
+ dic_msg5_elev['ref_thresh'] = 16
227
+ dic_msg5_elev['vel_thresh'] = 16
228
+ dic_msg5_elev['sw_thresh'] = 16
229
+ dic_msg5_elev['zdr_thres'] = 16
230
+ dic_msg5_elev['phi_thres'] = 16
231
+ dic_msg5_elev['rho_thres'] = 16
232
+ dic_msg5_elev['edge_angle_1'] = 5464
233
+ dic_msg5_elev['dop_prf_num_1'] = 4
234
+ dic_msg5_elev['dop_prf_pulse_count_1'] =59
235
+ dic_msg5_elev['spare_1'] = b'00'
236
+ dic_msg5_elev['edge_angle_2'] = 38232
237
+ dic_msg5_elev['dop_prf_num_2'] = 4
238
+ dic_msg5_elev['dop_prf_pulse_count_2'] = 59
239
+ dic_msg5_elev['spare_2'] = b'00'
240
+ dic_msg5_elev['edge_angle_3'] = 60984
241
+ dic_msg5_elev['dop_prf_num_3'] = 4
242
+ dic_msg5_elev['dop_prf_pulse_count_3'] = 59
243
+ dic_msg5_elev['spare_3'] = b'00'
244
+ cut_param.append(dic_msg5_elev.copy())
245
+
246
+ msg_5={}
247
+ msg_5['header'] = dic_msg_header.copy()
248
+ msg_5['msg5_header'] = dic_msg5_header.copy()
249
+ msg_5['cut_parameters'] = cut_param.copy()
250
+
251
+ self.radial_records = self._records.copy()
252
+ self._records.append(msg_5)
253
+
254
+ self.vcp = msg_5
255
+
256
+ # self.vcp = None
257
+ return
258
+
259
+ def close(self):
260
+ """ Close the file. """
261
+ self._fh.close()
262
+
263
+ def location(self):
264
+ """
265
+ Find the location of the radar.
266
+
267
+ Returns all zeros if location is not available.
268
+
269
+ Returns
270
+ -------
271
+ latitude : float
272
+ Latitude of the radar in degrees.
273
+ longitude : float
274
+ Longitude of the radar in degrees.
275
+ height : int
276
+ Height of radar and feedhorn in meters above mean sea level.
277
+
278
+ """
279
+ if self._msg_type == '31':
280
+ dic = self.radial_records[0]['VOL']
281
+ height = dic['height'] + dic['feedhorn_height']
282
+ return dic['lat'], dic['lon'], height
283
+ else:
284
+ return 0.0, 0.0, 0.0
285
+
286
+ def scan_info(self, scans=None):
287
+ """
288
+ Return a list of dictionaries with scan information.
289
+
290
+ Parameters
291
+ ----------
292
+ scans : list ot None
293
+ Scans (0 based) for which ray (radial) azimuth angles will be
294
+ retrieved. None (the default) will return the angles for all
295
+ scans in the volume.
296
+
297
+ Returns
298
+ -------
299
+ scan_info : list, optional
300
+ A list of the scan performed with a dictionary with keys
301
+ 'moments', 'ngates', 'nrays', 'first_gate' and 'gate_spacing'
302
+ for each scan. The 'moments', 'ngates', 'first_gate', and
303
+ 'gate_spacing' keys are lists of the NEXRAD moments and gate
304
+ information for that moment collected during the specific scan.
305
+ The 'nrays' key provides the number of radials collected in the
306
+ given scan.
307
+
308
+ """
309
+ info = []
310
+
311
+ if scans is None:
312
+ scans = range(self.nscans)
313
+ for scan in scans:
314
+ nrays = self.get_nrays(scan)
315
+ if nrays < 2:
316
+ self.nscans -= 1
317
+ continue
318
+ msg31_number = self.scan_msgs[scan][0]
319
+ msg = self.radial_records[msg31_number]
320
+ nexrad_moments = ['REF', 'VEL', 'SW', 'ZDR', 'PHI', 'RHO', 'CFP']
321
+ moments = [f for f in nexrad_moments if f in msg]
322
+ ngates = [msg[f]['ngates'] for f in moments]
323
+ gate_spacing = [msg[f]['gate_spacing'] for f in moments]
324
+ first_gate = [msg[f]['first_gate'] for f in moments]
325
+ info.append({
326
+ 'nrays': nrays,
327
+ 'ngates': ngates,
328
+ 'gate_spacing': gate_spacing,
329
+ 'first_gate': first_gate,
330
+ 'moments': moments})
331
+ return info
332
+
333
+ def get_vcp_pattern(self):
334
+ """
335
+ Return the numerical volume coverage pattern (VCP) or None if unknown.
336
+ """
337
+ if self.vcp is None:
338
+ return None
339
+ else:
340
+ return self.vcp['msg5_header']['pattern_number']
341
+
342
+ def get_nrays(self, scan):
343
+ """
344
+ Return the number of rays in a given scan.
345
+
346
+ Parameters
347
+ ----------
348
+ scan : int
349
+ Scan of interest (0 based).
350
+
351
+ Returns
352
+ -------
353
+ nrays : int
354
+ Number of rays (radials) in the scan.
355
+
356
+ """
357
+ return len(self.scan_msgs[scan])
358
+
359
+ def get_range(self, scan_num, moment):
360
+ """
361
+ Return an array of gate ranges for a given scan and moment.
362
+
363
+ Parameters
364
+ ----------
365
+ scan_num : int
366
+ Scan number (0 based).
367
+ moment : 'REF', 'VEL', 'SW', 'ZDR', 'PHI', 'RHO', or 'CFP'
368
+ Moment of interest.
369
+
370
+ Returns
371
+ -------
372
+ range : ndarray
373
+ Range in meters from the antenna to the center of gate (bin).
374
+
375
+ """
376
+ dic = self.radial_records[self.scan_msgs[scan_num][0]][moment]
377
+ ngates = dic['ngates']
378
+ first_gate = dic['first_gate']
379
+ gate_spacing = dic['gate_spacing']
380
+ return np.arange(ngates) * gate_spacing + first_gate
381
+
382
+ # helper functions for looping over scans
383
+ def _msg_nums(self, scans):
384
+ """ Find the all message number for a list of scans. """
385
+ return np.concatenate([self.scan_msgs[i] for i in scans])
386
+
387
+ def _radial_array(self, scans, key):
388
+ """
389
+ Return an array of radial header elements for all rays in scans.
390
+ """
391
+ msg_nums = self._msg_nums(scans)
392
+ temp = [self.radial_records[i]['msg_header'][key] for i in msg_nums]
393
+ return np.array(temp)
394
+
395
+ def _radial_sub_array(self, scans, key):
396
+ """
397
+ Return an array of RAD or msg_header elements for all rays in scans.
398
+ """
399
+ msg_nums = self._msg_nums(scans)
400
+ if self._msg_type == '31':
401
+ tmp = [self.radial_records[i]['RAD'][key] for i in msg_nums]
402
+ else:
403
+ tmp = [self.radial_records[i]['msg_header'][key] for i in msg_nums]
404
+ return np.array(tmp)
405
+
406
+ def get_times(self, scans=None):
407
+ """
408
+ Retrieve the times at which the rays were collected.
409
+
410
+ Parameters
411
+ ----------
412
+ scans : list or None
413
+ Scans (0-based) to retrieve ray (radial) collection times from.
414
+ None (the default) will return the times for all scans in the
415
+ volume.
416
+
417
+ Returns
418
+ -------
419
+ time_start : Datetime
420
+ Initial time.
421
+ time : ndarray
422
+ Offset in seconds from the initial time at which the rays
423
+ in the requested scans were collected.
424
+
425
+ """
426
+ if scans is None:
427
+ scans = range(self.nscans)
428
+ days = self._radial_array(scans, 'collect_date')
429
+ secs = self._radial_array(scans, 'collect_ms') / 1000.
430
+ offset = timedelta(days=int(days[0]) - 1, seconds=int(secs[0]))
431
+ time_start = datetime(1970, 1, 1) + offset
432
+ time = secs - int(secs[0]) + (days - days[0]) * 86400
433
+ return time_start, time
434
+
435
+ def get_azimuth_angles(self, scans=None):
436
+ """
437
+ Retrieve the azimuth angles of all rays in the requested scans.
438
+
439
+ Parameters
440
+ ----------
441
+ scans : list ot None
442
+ Scans (0 based) for which ray (radial) azimuth angles will be
443
+ retrieved. None (the default) will return the angles for all
444
+ scans in the volume.
445
+
446
+ Returns
447
+ -------
448
+ angles : ndarray
449
+ Azimuth angles in degress for all rays in the requested scans.
450
+
451
+ """
452
+ if scans is None:
453
+ scans = range(self.nscans)
454
+ if self._msg_type == '1':
455
+ scale = 180 / (4096 * 8.)
456
+ else:
457
+ scale = 1.
458
+ return self._radial_array(scans, 'azimuth_angle') * scale
459
+
460
+ def get_elevation_angles(self, scans=None):
461
+ """
462
+ Retrieve the elevation angles of all rays in the requested scans.
463
+
464
+ Parameters
465
+ ----------
466
+ scans : list or None
467
+ Scans (0 based) for which ray (radial) azimuth angles will be
468
+ retrieved. None (the default) will return the angles for
469
+ all scans in the volume.
470
+
471
+ Returns
472
+ -------
473
+ angles : ndarray
474
+ Elevation angles in degress for all rays in the requested scans.
475
+
476
+ """
477
+ if scans is None:
478
+ scans = range(self.nscans)
479
+ if self._msg_type == '1':
480
+ scale = 180 / (4096 * 8.)
481
+ else:
482
+ scale = 1.
483
+ return self._radial_array(scans, 'elevation_angle') * scale
484
+
485
+ def get_target_angles(self, scans=None):
486
+ """
487
+ Retrieve the target elevation angle of the requested scans.
488
+
489
+ Parameters
490
+ ----------
491
+ scans : list or None
492
+ Scans (0 based) for which the target elevation angles will be
493
+ retrieved. None (the default) will return the angles for all
494
+ scans in the volume.
495
+
496
+ Returns
497
+ -------
498
+ angles : ndarray
499
+ Target elevation angles in degress for the requested scans.
500
+
501
+ """
502
+ if scans is None:
503
+ scans = range(self.nscans)
504
+ if self._msg_type == '31':
505
+ if self.vcp is not None:
506
+ cut_parameters = self.vcp['cut_parameters']
507
+ else:
508
+ cut_parameters = [{'elevation_angle': 0.0}] * self.nscans
509
+ scale = 360. / 65536.
510
+ return np.array([cut_parameters[i]['elevation_angle'] * scale
511
+ for i in scans], dtype='float32')
512
+ else:
513
+ scale = 180 / (4096 * 8.)
514
+ msgs = [self.radial_records[self.scan_msgs[i][0]] for i in scans]
515
+ return np.round(np.array(
516
+ [m['msg_header']['elevation_angle'] * scale for m in msgs],
517
+ dtype='float32'), 1)
518
+
519
+ def get_nyquist_vel(self, scans=None):
520
+ """
521
+ Retrieve the Nyquist velocities of the requested scans.
522
+
523
+ Parameters
524
+ ----------
525
+ scans : list or None
526
+ Scans (0 based) for which the Nyquist velocities will be
527
+ retrieved. None (the default) will return the velocities for all
528
+ scans in the volume.
529
+
530
+ Returns
531
+ -------
532
+ velocities : ndarray
533
+ Nyquist velocities (in m/s) for the requested scans.
534
+
535
+ """
536
+ if scans is None:
537
+ scans = range(self.nscans)
538
+ return self._radial_sub_array(scans, 'nyquist_vel') * 0.01
539
+
540
+ def get_unambigous_range(self, scans=None):
541
+ """
542
+ Retrieve the unambiguous range of the requested scans.
543
+
544
+ Parameters
545
+ ----------
546
+ scans : list or None
547
+ Scans (0 based) for which the unambiguous range will be retrieved.
548
+ None (the default) will return the range for all scans in the
549
+ volume.
550
+
551
+ Returns
552
+ -------
553
+ unambiguous_range : ndarray
554
+ Unambiguous range (in meters) for the requested scans.
555
+
556
+ """
557
+ if scans is None:
558
+ scans = range(self.nscans)
559
+ # unambiguous range is stored in tenths of km, x100 for meters
560
+ return self._radial_sub_array(scans, 'unambig_range') * 100.
561
+
562
+ def get_data(self, moment, max_ngates, scans=None, raw_data=False):
563
+ """
564
+ Retrieve moment data for a given set of scans.
565
+
566
+ Masked points indicate that the data was not collected, below
567
+ threshold or is range folded.
568
+
569
+ Parameters
570
+ ----------
571
+ moment : 'REF', 'VEL', 'SW', 'ZDR', 'PHI', 'RHO', or 'CFP'
572
+ Moment for which to to retrieve data.
573
+ max_ngates : int
574
+ Maximum number of gates (bins) in any ray.
575
+ requested.
576
+ raw_data : bool
577
+ True to return the raw data, False to perform masking as well as
578
+ applying the appropiate scale and offset to the data. When
579
+ raw_data is True values of 1 in the data likely indicate that
580
+ the gate was not present in the sweep, in some cases in will
581
+ indicate range folded data.
582
+ scans : list or None.
583
+ Scans to retrieve data from (0 based). None (the default) will
584
+ get the data for all scans in the volume.
585
+
586
+ Returns
587
+ -------
588
+ data : ndarray
589
+
590
+ """
591
+ if scans is None:
592
+ scans = range(self.nscans)
593
+
594
+ # determine the number of rays
595
+ msg_nums = self._msg_nums(scans)
596
+ nrays = len(msg_nums)
597
+ # extract the data
598
+ set_datatype = False
599
+ data = np.ones((nrays, max_ngates), '>B')
600
+ for i, msg_num in enumerate(msg_nums):
601
+ msg = self.radial_records[msg_num]
602
+ if moment not in msg.keys():
603
+ continue
604
+ if not set_datatype:
605
+ data = data.astype('>'+self._bits_to_code(msg, moment))
606
+ set_datatype = True
607
+
608
+ ngates = min(msg[moment]['ngates'], max_ngates,
609
+ len(msg[moment]['data']))
610
+ data[i, :ngates] = msg[moment]['data'][:ngates]
611
+ # return raw data if requested
612
+ if raw_data:
613
+ return data
614
+
615
+ # mask, scan and offset, assume that the offset and scale
616
+ # are the same in all scans/gates
617
+ for scan in scans: # find a scan which contains the moment
618
+ msg_num = self.scan_msgs[scan][0]
619
+ msg = self.radial_records[msg_num]
620
+ if moment in msg.keys():
621
+ offset = np.float32(msg[moment]['offset'])
622
+ scale = np.float32(msg[moment]['scale'])
623
+ mask = data <= 1
624
+ scaled_data = (data - offset) / scale
625
+ return np.ma.array(scaled_data, mask=mask)
626
+
627
+ # moment is not present in any scan, mask all values
628
+ return np.ma.masked_less_equal(data, 1)
629
+
630
+
631
+ def _bits_to_code(self,msg, moment):
632
+ """
633
+ Convert number of bits to the proper code for unpacking.
634
+ Based on the code found in MetPy:
635
+ https://github.com/Unidata/MetPy/blob/40d5c12ab341a449c9398508bd41
636
+ d010165f9eeb/src/metpy/io/_tools.py#L313-L321
637
+ """
638
+ if msg['header']['type'] == 1:
639
+ word_size = msg[moment]['data'].dtype
640
+ if word_size == 'uint16':
641
+ return 'H'
642
+ elif word_size == 'uint8':
643
+ return 'B'
644
+ else:
645
+ warnings.warn(
646
+ ('Unsupported bit size: %s. Returning "B"', word_size))
647
+ return 'B'
648
+
649
+ elif msg['header']['type'] == 31:
650
+ word_size = msg[moment]['word_size']
651
+ if word_size == 16:
652
+ return 'H'
653
+ elif word_size == 8:
654
+ return 'B'
655
+ else:
656
+ warnings.warn(
657
+ ('Unsupported bit size: %s. Returning "B"', word_size))
658
+ return 'B'
659
+ else:
660
+ raise TypeError("Unsupported msg type %s", msg['header']['type'])
661
+
662
+
663
+ def _decompress_records(self,file_handler):
664
+ """
665
+ Decompressed the records from an BZ2 compressed Archive 2 file.
666
+ """
667
+ file_handler.seek(0)
668
+ cbuf = file_handler.read() # read all data from the file
669
+ decompressor = bz2.BZ2Decompressor()
670
+ skip = _structure_size(VOLUME_HEADER) + CONTROL_WORD_SIZE
671
+ buf = decompressor.decompress(cbuf[skip:])
672
+ while len(decompressor.unused_data):
673
+ cbuf = decompressor.unused_data
674
+ decompressor = bz2.BZ2Decompressor()
675
+ buf += decompressor.decompress(cbuf[CONTROL_WORD_SIZE:])
676
+
677
+ return buf[COMPRESSION_RECORD_SIZE:]
678
+
679
+
680
+ def _get_record_from_buf(self,buf, pos):
681
+ """ Retrieve and unpack a NEXRAD record from a buffer. """
682
+ dic={}
683
+ new_pos = self._get_msg31_from_buf(buf, pos, dic)
684
+
685
+ return new_pos, dic
686
+
687
+
688
+ def _get_msg31_from_buf(self,buf, pos, dic):
689
+ """ Retrieve and unpack a MSG31 record from a buffer. """
690
+
691
+ dic_rhb = _unpack_from_buf(buf, pos, RADIAL_HEAD_BLOCK)
692
+ pos2 = pos + _structure_size(RADIAL_HEAD_BLOCK)
693
+
694
+ varnum = dic_rhb['mom_num']
695
+
696
+ # 构建msg header
697
+ outdic_mh = dict(zip([i[0] for i in MSG_HEADER],
698
+ [k for k in np.zeros(len(MSG_HEADER))]))
699
+ outdic_mh['size'] = 3440 # 初始值,后面会改
700
+ outdic_mh['channels'] = 8
701
+ outdic_mh['type'] = 31
702
+ outdic_mh['seq_id'] = 1
703
+ Juliandate = int(dic_rhb['seconds'] / 86400) # 一天有86400秒
704
+ Seconds = 1000 * (dic_rhb['seconds'] - Juliandate * 86400)
705
+ Juliandate = Juliandate + 1
706
+ outdic_mh['date'] = Juliandate
707
+ outdic_mh['ms'] = Seconds
708
+ outdic_mh['segments'] = 1
709
+ outdic_mh['seg_num'] = 1
710
+
711
+ # 构建msg31 header
712
+ outdic31 = dict(zip([i[0] for i in MSG_31],
713
+ [k for k in np.zeros(len(MSG_31))]))
714
+ # ang_reso = cutinfo[dic_rhb['ele_num'] - 1]['ang_reso']
715
+ outdic31['id'] = self.radname.encode()
716
+ outdic31['collect_ms'] = Seconds
717
+ outdic31['collect_date'] = Juliandate
718
+ outdic31['azimuth_number'] = dic_rhb['radial_num']
719
+ outdic31['azimuth_angle'] = dic_rhb['azimuth']
720
+ outdic31['compress_flag'] = 0
721
+ outdic31['spare_0'] = 0
722
+ outdic31['radial_length'] = 0
723
+ outdic31['azimuth_resolution'] = 2# 2表示1度,1表示0.5度int(2 * ang_reso)
724
+ outdic31['radial_stats'] = dic_rhb['radial_stats']
725
+
726
+
727
+ # print(dic_rhb['radial_stats'])
728
+ outdic31['elevation_number'] = dic_rhb['ele_num']
729
+ outdic31['cut_sector'] = 1
730
+ outdic31['elevation_angle'] = dic_rhb['elevation']
731
+ outdic31['radial_blanking'] = 0
732
+ outdic31['azimuth_mode'] = 25
733
+ outdic31['block_count'] = 9
734
+ # pointer for VOLUME_DATA_BLOCK based on MSG31 (68)
735
+ # Volume Data Constant XVII-E
736
+ outdic31['block_pointer_1'] = _structure_size(MSG_31)
737
+ # Elevation Data Constant XVII-F
738
+ outdic31['block_pointer_2'] = outdic31['block_pointer_1'] + _structure_size(VOLUME_DATA_BLOCK)
739
+ # Radial Data Constant XVII-H
740
+ outdic31['block_pointer_3'] = outdic31['block_pointer_2'] + _structure_size(ELEVATION_DATA_BLOCK)
741
+ # Moment "REF" XVII-{B/I} 152?
742
+ outdic31['block_pointer_4'] = 0
743
+ # Moment "VEL"
744
+ outdic31['block_pointer_5'] = 0
745
+ # Moment "SW"
746
+ outdic31['block_pointer_6'] = 0
747
+ # Moment "ZDR"
748
+ outdic31['block_pointer_7'] = 0
749
+ # Moment "PHI"
750
+ outdic31['block_pointer_8'] = 0
751
+ # Moment "RHO"
752
+ outdic31['block_pointer_9'] = 0
753
+
754
+
755
+ # Volume Data Constant Type
756
+ outdic_vol = dict(zip([i[0] for i in VOLUME_DATA_BLOCK],
757
+ [k for k in np.zeros(len(VOLUME_DATA_BLOCK))]))
758
+ outdic_vol['block_type'] = b'R'
759
+ outdic_vol['data_name'] = b'VOL'
760
+ outdic_vol['lrtup'] = _structure_size(VOLUME_DATA_BLOCK)
761
+ outdic_vol['version_major'] = 2
762
+ outdic_vol['version_minor'] = 0
763
+
764
+ outdic_vol['lat'] = self.dic_stcfg['lat']
765
+ outdic_vol['lon'] = self.dic_stcfg['lon']
766
+
767
+
768
+ if self.dic_stcfg['grd_height'] > 8000:
769
+ outdic_vol['height'] = int(self.dic_stcfg['grd_height']/1000)
770
+ else:
771
+ outdic_vol['height'] = int(self.dic_stcfg['grd_height'])
772
+ if self.dic_stcfg['ana_height'] > 8000:
773
+ outdic_vol['feedhorn_height'] = int(self.dic_stcfg['ana_height']/1000)
774
+ else:
775
+ outdic_vol['feedhorn_height'] = int(self.dic_stcfg['ana_height'])
776
+
777
+ outdic_vol['refl_calib'] = 1.0
778
+ outdic_vol['power_h'] = 0.0
779
+ outdic_vol['power_v'] = 0.0
780
+ outdic_vol['diff_refl_calib'] = 0.0
781
+ outdic_vol['init_phase'] = 0.0
782
+ outdic_vol['vcp'] = self.vcp_type_num
783
+ outdic_vol['spare'] = b'aa'
784
+
785
+ # Elevation Data Constant Type
786
+
787
+ outdic_elv = dict(zip([i[0] for i in ELEVATION_DATA_BLOCK],
788
+ [k for k in np.zeros(len(ELEVATION_DATA_BLOCK))]))
789
+
790
+ outdic_elv['block_type'] = b'R'
791
+ outdic_elv['data_name'] = b'ELV'
792
+ outdic_elv['lrtup'] = _structure_size(ELEVATION_DATA_BLOCK)
793
+ outdic_elv['atmos'] = -12
794
+ outdic_elv['refl_calib'] = -44.625
795
+
796
+ # Radial Data Constant Type
797
+ outdic_rad = dict(zip([i[0] for i in RADIAL_DATA_BLOCK],
798
+ [k for k in np.zeros(len(RADIAL_DATA_BLOCK))]))
799
+
800
+ outdic_rad['block_type'] = b'R'
801
+ outdic_rad['data_name'] = b'RAD'
802
+ outdic_rad['lrtup'] = _structure_size(RADIAL_DATA_BLOCK)
803
+ outdic_rad['unambig_range'] = int(self.cutinfo[dic_rhb['ele_num'] - 1]['max_range1'] / 100)
804
+ outdic_rad['noise_h'] = -32.0
805
+ outdic_rad['noise_v'] = 0
806
+ outdic_rad['nyquist_vel'] = 100 * int(self.cutinfo[dic_rhb['ele_num'] - 1]['nyquist'])
807
+ outdic_rad['spare'] = b'aa'
808
+ outdic_rad['calib_dbz0_h'] = -44.95
809
+ outdic_rad['calib_dbz0_v'] = -44.73
810
+
811
+ # dic = {}
812
+ dic['header'] = outdic_mh
813
+ dic['msg_header'] = outdic31
814
+ dic['VOL'] = outdic_vol
815
+ dic['ELV'] = outdic_elv
816
+ dic['RAD'] = outdic_rad
817
+
818
+ # 构建radial data
819
+ for nn in range(0, varnum):
820
+ block_name, block_dic,ptr = self._get_msg31_data_block(buf,pos2,dic_rhb)
821
+ pos2 = ptr
822
+
823
+ if not block_name is None:
824
+ dic[block_name] = block_dic
825
+
826
+ # dic['header'] = msg_31_header
827
+ new_pos = pos2
828
+ return new_pos
829
+
830
+
831
+ def _get_msg31_data_block(self,buf,ptr,dic_rhb ):
832
+ """ Unpack a msg_31 data block into a dictionary. """
833
+ dic_rh = _unpack_from_buf(buf, ptr, RADIAL_HEAD)
834
+ ptr = ptr + _structure_size(RADIAL_HEAD)
835
+ data_type = dic_rh['data_type']
836
+ real_buflen = dic_rh['length']
837
+ real_gates = int(dic_rh['length'] / dic_rh['bin_len'])
838
+ ref_reso = int(self.cutinfo[dic_rhb['ele_num'] - 1]['ref_reso'])
839
+ vel_reso = int(self.cutinfo[dic_rhb['ele_num'] - 1]['vel_reso'])
840
+
841
+ if dic_rh['bin_len'] == 1:
842
+ data = np.frombuffer(buf[ptr: ptr + real_buflen], '<u1')
843
+ ptr = ptr + real_buflen
844
+ elif dic_rh['bin_len'] == 2:
845
+ # data = np.zeros(real_buflen, dtype='uint16')
846
+ data = np.frombuffer(buf[ptr: ptr + real_buflen], '<u2')
847
+ ptr = ptr + real_buflen
848
+ else:
849
+ return None, None,ptr
850
+ # dic = {}
851
+ # dic['data'] = data
852
+ # dic['length'] = real_buflen
853
+ reso = 0
854
+ if data_type == 2:
855
+ block_name = 'REF'
856
+ reso = ref_reso
857
+ elif data_type == 3:
858
+ block_name = 'VEL'
859
+ reso = vel_reso
860
+ elif data_type == 4:
861
+ block_name = 'SW'
862
+ reso = vel_reso
863
+ elif data_type == 7:
864
+ block_name = 'ZDR'
865
+ reso = ref_reso
866
+ elif data_type == 9:
867
+ block_name = 'RHO'
868
+ reso = ref_reso
869
+ elif data_type == 10:
870
+ block_name = 'PHI'
871
+ reso = ref_reso
872
+ else:
873
+ return None, None,ptr
874
+
875
+ outdic = dict(zip([i[0] for i in GENERIC_DATA_BLOCK],
876
+ [k for k in np.zeros(len(GENERIC_DATA_BLOCK))]))
877
+ outdic['block_type'] = b'D'
878
+ outdic['data_name'] = block_name.encode()
879
+ outdic['reserved'] = 0
880
+ outdic['ngates'] = real_gates
881
+ outdic['first_gate'] = reso # self.cutinfo[dic_rhb['ele_num'] - 1]['start_range']
882
+ outdic['gate_spacing'] = reso
883
+ outdic['thresh'] = 100
884
+ outdic['snr_thres'] = 16
885
+ outdic['flags'] = 0
886
+ outdic['word_size'] = 8 * dic_rh['bin_len']
887
+ outdic['scale'] = float(dic_rh['scale'])
888
+ outdic['offset'] = float(dic_rh['offset'])
889
+
890
+ outdic['data'] = data
891
+
892
+
893
+ return block_name, outdic,ptr
894
+
895
+
896
+ def _get_msg1_from_buf(self,buf, pos, dic):
897
+ """ Retrieve and unpack a MSG1 record from a buffer. """
898
+ msg_header_size = _structure_size(MSG_HEADER)
899
+ msg1_header = _unpack_from_buf(buf, pos + msg_header_size, MSG_1)
900
+ dic['msg_header'] = msg1_header
901
+
902
+ sur_nbins = int(msg1_header['sur_nbins'])
903
+ doppler_nbins = int(msg1_header['doppler_nbins'])
904
+
905
+ sur_step = int(msg1_header['sur_range_step'])
906
+ doppler_step = int(msg1_header['doppler_range_step'])
907
+
908
+ sur_first = int(msg1_header['sur_range_first'])
909
+ doppler_first = int(msg1_header['doppler_range_first'])
910
+ if doppler_first > 2**15:
911
+ doppler_first = doppler_first - 2**16
912
+
913
+ if msg1_header['sur_pointer']:
914
+ offset = pos + msg_header_size + msg1_header['sur_pointer']
915
+ data = np.frombuffer(buf[offset:offset+sur_nbins], '>u1')
916
+ dic['REF'] = {
917
+ 'ngates': sur_nbins,
918
+ 'gate_spacing': sur_step,
919
+ 'first_gate': sur_first,
920
+ 'data': data,
921
+ 'scale': 2.,
922
+ 'offset': 66.,
923
+ }
924
+ if msg1_header['vel_pointer']:
925
+ offset = pos + msg_header_size + msg1_header['vel_pointer']
926
+ data = np.frombuffer(buf[offset:offset+doppler_nbins], '>u1')
927
+ dic['VEL'] = {
928
+ 'ngates': doppler_nbins,
929
+ 'gate_spacing': doppler_step,
930
+ 'first_gate': doppler_first,
931
+ 'data': data,
932
+ 'scale': 2.,
933
+ 'offset': 129.0,
934
+ }
935
+ if msg1_header['doppler_resolution'] == 4:
936
+ # 1 m/s resolution velocity, offset remains 129.
937
+ dic['VEL']['scale'] = 1.
938
+ if msg1_header['width_pointer']:
939
+ offset = pos + msg_header_size + msg1_header['width_pointer']
940
+ data = np.frombuffer(buf[offset:offset+doppler_nbins], '>u1')
941
+ dic['SW'] = {
942
+ 'ngates': doppler_nbins,
943
+ 'gate_spacing': doppler_step,
944
+ 'first_gate': doppler_first,
945
+ 'data': data,
946
+ 'scale': 2.,
947
+ 'offset': 129.0,
948
+ }
949
+ return pos + RECORD_SIZE
950
+
951
+
952
+
953
+ def _structure_size(structure):
954
+ """ Find the size of a structure in bytes. """
955
+ return struct.calcsize('<' + ''.join([i[1] for i in structure]))
956
+
957
+
958
+ def _unpack_from_buf( buf, pos, structure):
959
+ """ Unpack a structure from a buffer. """
960
+ size = _structure_size(structure)
961
+ return _unpack_structure(buf[pos:pos + size], structure)
962
+
963
+
964
+ def _unpack_structure(string, structure):
965
+ """ Unpack a structure from a string """
966
+ fmt = '<' + ''.join([i[1] for i in structure]) # little-endian
967
+ lst = struct.unpack(fmt, string)
968
+ return dict(zip([i[0] for i in structure], lst))
969
+
970
+
971
+ # NEXRAD Level II file structures and sizes
972
+ # The deails on these structures are documented in:
973
+ # "Interface Control Document for the Achive II/User" RPG Build 12.0
974
+ # Document Number 2620010E
975
+ # and
976
+ # "Interface Control Document for the RDA/RPG" Open Build 13.0
977
+ # Document Number 2620002M
978
+ # Tables and page number refer to those in the second document unless
979
+ # otherwise noted.
980
+ RECORD_SIZE = 2432
981
+ COMPRESSION_RECORD_SIZE = 12
982
+ CONTROL_WORD_SIZE = 4
983
+
984
+ # format of structure elements
985
+ # section 3.2.1, page 3-2
986
+ CODE1 = 'B'
987
+ CODE2 = 'H'
988
+ INT1 = 'B'
989
+ INT2 = 'H'
990
+ INT4 = 'I'
991
+ REAL4 = 'f'
992
+ REAL8 = 'd'
993
+ SINT1 = 'b'
994
+ SINT2 = 'h'
995
+ SINT4 = 'i'
996
+
997
+ # Figure 1 in Interface Control Document for the Archive II/User
998
+ # page 7-2
999
+ VOLUME_HEADER = (
1000
+ ('tape', '9s'),
1001
+ ('extension', '3s'),
1002
+ ('date', 'I'),
1003
+ ('time', 'I'),
1004
+ ('icao', '4s')
1005
+ )
1006
+
1007
+ # Table II Message Header Data
1008
+ # page 3-7
1009
+ MSG_HEADER = (
1010
+ ('size', INT2), # size of data, no including header
1011
+ ('channels', INT1),
1012
+ ('type', INT1),
1013
+ ('seq_id', INT2),
1014
+ ('date', INT2),
1015
+ ('ms', INT4),
1016
+ ('segments', INT2),
1017
+ ('seg_num', INT2),
1018
+ )
1019
+
1020
+ # Table XVII Digital Radar Generic Format Blocks (Message Type 31)
1021
+ # pages 3-87 to 3-89
1022
+ MSG_31 = (
1023
+ ('id', '4s'), # 0-3
1024
+ ('collect_ms', INT4), # 4-7
1025
+ ('collect_date', INT2), # 8-9
1026
+ ('azimuth_number', INT2), # 10-11
1027
+ ('azimuth_angle', REAL4), # 12-15
1028
+ ('compress_flag', CODE1), # 16
1029
+ ('spare_0', INT1), # 17
1030
+ ('radial_length', INT2), # 18-19
1031
+ ('azimuth_resolution', CODE1), # 20
1032
+ ('radial_spacing', CODE1), # 21
1033
+ ('elevation_number', INT1), # 22
1034
+ ('cut_sector', INT1), # 23
1035
+ ('elevation_angle', REAL4), # 24-27
1036
+ ('radial_blanking', CODE1), # 28
1037
+ ('azimuth_mode', SINT1), # 29
1038
+ ('block_count', INT2), # 30-31
1039
+ ('block_pointer_1', INT4), # 32-35 Volume Data Constant XVII-E
1040
+ ('block_pointer_2', INT4), # 36-39 Elevation Data Constant XVII-F
1041
+ ('block_pointer_3', INT4), # 40-43 Radial Data Constant XVII-H
1042
+ ('block_pointer_4', INT4), # 44-47 Moment "REF" XVII-{B/I}
1043
+ ('block_pointer_5', INT4), # 48-51 Moment "VEL"
1044
+ ('block_pointer_6', INT4), # 52-55 Moment "SW"
1045
+ ('block_pointer_7', INT4), # 56-59 Moment "ZDR"
1046
+ ('block_pointer_8', INT4), # 60-63 Moment "PHI"
1047
+ ('block_pointer_9', INT4), # 64-67 Moment "RHO"
1048
+ ('block_pointer_10', INT4), # Moment "CFP"
1049
+ )
1050
+
1051
+
1052
+ # Table III Digital Radar Data (Message Type 1)
1053
+ # pages 3-7 to
1054
+ MSG_1 = (
1055
+ ('collect_ms', INT4), # 0-3
1056
+ ('collect_date', INT2), # 4-5
1057
+ ('unambig_range', SINT2), # 6-7
1058
+ ('azimuth_angle', CODE2), # 8-9
1059
+ ('azimuth_number', INT2), # 10-11
1060
+ ('radial_status', CODE2), # 12-13
1061
+ ('elevation_angle', INT2), # 14-15
1062
+ ('elevation_number', INT2), # 16-17
1063
+ ('sur_range_first', CODE2), # 18-19
1064
+ ('doppler_range_first', CODE2), # 20-21
1065
+ ('sur_range_step', CODE2), # 22-23
1066
+ ('doppler_range_step', CODE2), # 24-25
1067
+ ('sur_nbins', INT2), # 26-27
1068
+ ('doppler_nbins', INT2), # 28-29
1069
+ ('cut_sector_num', INT2), # 30-31
1070
+ ('calib_const', REAL4), # 32-35
1071
+ ('sur_pointer', INT2), # 36-37
1072
+ ('vel_pointer', INT2), # 38-39
1073
+ ('width_pointer', INT2), # 40-41
1074
+ ('doppler_resolution', CODE2), # 42-43
1075
+ ('vcp', INT2), # 44-45
1076
+ ('spare_1', '8s'), # 46-53
1077
+ ('spare_2', '2s'), # 54-55
1078
+ ('spare_3', '2s'), # 56-57
1079
+ ('spare_4', '2s'), # 58-59
1080
+ ('nyquist_vel', SINT2), # 60-61
1081
+ ('atmos_attenuation', SINT2), # 62-63
1082
+ ('threshold', SINT2), # 64-65
1083
+ ('spot_blank_status', INT2), # 66-67
1084
+ ('spare_5', '32s'), # 68-99
1085
+ # 100+ reflectivity, velocity and/or spectral width data, CODE1
1086
+ )
1087
+
1088
+ # Table XI Volume Coverage Pattern Data (Message Type 5 & 7)
1089
+ # pages 3-51 to 3-54
1090
+ MSG_5 = (
1091
+ ('msg_size', INT2),
1092
+ ('pattern_type', CODE2),
1093
+ ('pattern_number', INT2),
1094
+ ('num_cuts', INT2),
1095
+ ('clutter_map_group', INT2),
1096
+ ('doppler_vel_res', CODE1), # 2: 0.5 degrees, 4: 1.0 degrees
1097
+ ('pulse_width', CODE1), # 2: short, 4: long
1098
+ ('spare', '10s') # halfwords 7-11 (10 bytes, 5 halfwords)
1099
+ )
1100
+
1101
+ MSG_5_ELEV = (
1102
+ ('elevation_angle', CODE2), # scaled by 360/65536 for value in degrees.
1103
+ ('channel_config', CODE1),
1104
+ ('waveform_type', CODE1),
1105
+ ('super_resolution', CODE1),
1106
+ ('prf_number', INT1),
1107
+ ('prf_pulse_count', INT2),
1108
+ ('azimuth_rate', CODE2),
1109
+ ('ref_thresh', SINT2),
1110
+ ('vel_thresh', SINT2),
1111
+ ('sw_thresh', SINT2),
1112
+ ('zdr_thres', SINT2),
1113
+ ('phi_thres', SINT2),
1114
+ ('rho_thres', SINT2),
1115
+ ('edge_angle_1', CODE2),
1116
+ ('dop_prf_num_1', INT2),
1117
+ ('dop_prf_pulse_count_1', INT2),
1118
+ ('spare_1', '2s'),
1119
+ ('edge_angle_2', CODE2),
1120
+ ('dop_prf_num_2', INT2),
1121
+ ('dop_prf_pulse_count_2', INT2),
1122
+ ('spare_2', '2s'),
1123
+ ('edge_angle_3', CODE2),
1124
+ ('dop_prf_num_3', INT2),
1125
+ ('dop_prf_pulse_count_3', INT2),
1126
+ ('spare_3', '2s'),
1127
+ )
1128
+
1129
+ # Table XVII-B Data Block (Descriptor of Generic Data Moment Type)
1130
+ # pages 3-90 and 3-91
1131
+ GENERIC_DATA_BLOCK = (
1132
+ ('block_type', '1s'),
1133
+ ('data_name', '3s'), # VEL, REF, SW, RHO, PHI, ZDR
1134
+ ('reserved', INT4),
1135
+ ('ngates', INT2),
1136
+ ('first_gate', SINT2),
1137
+ ('gate_spacing', SINT2),
1138
+ ('thresh', SINT2),
1139
+ ('snr_thres', SINT2),
1140
+ ('flags', CODE1),
1141
+ ('word_size', INT1),
1142
+ ('scale', REAL4),
1143
+ ('offset', REAL4),
1144
+ # then data
1145
+ )
1146
+
1147
+ # Table XVII-E Data Block (Volume Data Constant Type)
1148
+ # page 3-92
1149
+ VOLUME_DATA_BLOCK = (
1150
+ ('block_type', '1s'),
1151
+ ('data_name', '3s'),
1152
+ ('lrtup', INT2),
1153
+ ('version_major', INT1),
1154
+ ('version_minor', INT1),
1155
+ ('lat', REAL4),
1156
+ ('lon', REAL4),
1157
+ ('height', SINT2),
1158
+ ('feedhorn_height', INT2),
1159
+ ('refl_calib', REAL4),
1160
+ ('power_h', REAL4),
1161
+ ('power_v', REAL4),
1162
+ ('diff_refl_calib', REAL4),
1163
+ ('init_phase', REAL4),
1164
+ ('vcp', INT2),
1165
+ ('spare', '2s'),
1166
+ )
1167
+
1168
+ # Table XVII-F Data Block (Elevation Data Constant Type)
1169
+ # page 3-93
1170
+ ELEVATION_DATA_BLOCK = (
1171
+ ('block_type', '1s'),
1172
+ ('data_name', '3s'),
1173
+ ('lrtup', INT2),
1174
+ ('atmos', SINT2),
1175
+ ('refl_calib', REAL4),
1176
+ )
1177
+
1178
+ # Table XVII-H Data Block (Radial Data Constant Type)
1179
+ # pages 3-93
1180
+ RADIAL_DATA_BLOCK = (
1181
+ ('block_type', '1s'),
1182
+ ('data_name', '3s'),
1183
+ ('lrtup', INT2),
1184
+ ('unambig_range', SINT2),
1185
+ ('noise_h', REAL4),
1186
+ ('noise_v', REAL4),
1187
+ ('nyquist_vel', SINT2),
1188
+ ('spare', '2s')
1189
+ )
1190
+
1191
+ ##====================================================================================================
1192
+ CODE1 = 'B'
1193
+ CODE2 = 'H'
1194
+ INT1 = 'B'
1195
+ INT2 = 'H'
1196
+ INT4 = 'I'
1197
+ REAL4 = 'f'
1198
+ REAL8 = 'd'
1199
+ SINT1 = 'b'
1200
+ SINT2 = 'h'
1201
+ SINT4 = 'i'
1202
+
1203
+ # 下面的文件头信息来源于 北京敏视达公司的双偏振天气雷达基数据格式,2016
1204
+
1205
+ # 表2-2
1206
+ GENERIC_HEADER = (
1207
+ ('magic_number', INT4),
1208
+ ('major_version', INT2),
1209
+ ('minor_version', INT2),
1210
+ ('generic_type', INT4),
1211
+ ('product_type', INT4),
1212
+ ('reserved', '16s')
1213
+ )
1214
+
1215
+ # 表2-3
1216
+ SITE_CONFIG = (
1217
+ ('site_code', '8s'),
1218
+ ('site_name', '32s'),
1219
+ ('lat', REAL4),
1220
+ ('lon', REAL4),
1221
+ ('ana_height', INT4),
1222
+ ('grd_height', INT4),
1223
+ ('freq', REAL4),
1224
+ ('beamwidth_h', REAL4),
1225
+ ('beamwidth_v', REAL4),
1226
+ ('rda_version', INT4),
1227
+ ('radar_type', INT2),
1228
+ ('reserved', '54s')
1229
+ )
1230
+
1231
+ # 表2-4
1232
+ TASK_CONFIG = (
1233
+ ('task_name', '32s'),
1234
+ ('task_disp', '128s'),
1235
+ ('pol_type', INT4),
1236
+ ('scan_type', INT4),
1237
+ ('pulse_wid', INT4),
1238
+ ('scan_stime', INT4),
1239
+ ('cut_number', INT4),
1240
+ ('hor_noise', REAL4),
1241
+ ('ver_noise', REAL4),
1242
+ ('hor_calib', REAL4),
1243
+ ('ver_calib', REAL4),
1244
+ ('hor_noise_t', REAL4),
1245
+ ('ver_noise_t', REAL4),
1246
+ ('zdr_calib', REAL4),
1247
+ ('phi_calib', REAL4),
1248
+ ('ldr_calib', REAL4),
1249
+ ('reserved', '40s')
1250
+ )
1251
+
1252
+ # 表2-5
1253
+ SCAN_CONFIG = (
1254
+ ('process_mod', INT4),
1255
+ ('wave_form', INT4),
1256
+ ('prf1', REAL4),
1257
+ ('prf2', REAL4),
1258
+ ('dias_mode', INT4),
1259
+ ('azimuth', REAL4),
1260
+ ('elevation', REAL4),
1261
+ ('start_angle', REAL4),
1262
+ ('end_angle', REAL4),
1263
+ ('ang_reso', REAL4),
1264
+ ('scan_speed', REAL4),
1265
+ ('ref_reso', INT4),
1266
+ ('vel_reso', INT4),
1267
+ ('max_range1', INT4),
1268
+ ('max_range2', INT4),
1269
+ ('start_range', INT4),
1270
+ ('sample_num1', INT4),
1271
+ ('sample_num2', INT4),
1272
+ ('phase_mode', INT4),
1273
+ ('atmo_loss', REAL4),
1274
+ ('nyquist', REAL4),
1275
+ ('mom_mask', REAL8),
1276
+ ('mom_size_mask', REAL8),
1277
+ ('mgk_fil_mask', INT4),
1278
+ ('sqi_thr', REAL4),
1279
+ ('sig_thr', REAL4),
1280
+ ('csr_thr', REAL4),
1281
+ ('log_thr', REAL4),
1282
+ ('cpa_thr', REAL4),
1283
+ ('pmi_thr', REAL4),
1284
+ ('dplog_thr', REAL4),
1285
+ ('thr_reserved', '4s'),
1286
+ ('dbt_mask', INT4),
1287
+ ('dbz_mask', INT4),
1288
+ ('vel_mask', INT4),
1289
+ ('sw_mask', INT4),
1290
+ ('dp_mask', INT4),
1291
+ ('mask_reserved', '12s'),
1292
+ ('scan_sync', INT4),
1293
+ ('direction', INT4),
1294
+ ('grd_clutter_clsify', INT2),
1295
+ ('grd_clutter_filt', INT2),
1296
+ ('grd_clutter_filt_ntwidth', INT2),
1297
+ ('grd_clutter_filt_window', INT2),
1298
+ ('reserved', '72s')
1299
+ )
1300
+
1301
+ # 表3-1
1302
+ RADIAL_HEAD_BLOCK = (
1303
+ ('radial_stats', INT4),
1304
+ ('spot_blank', INT4),
1305
+ ('seq_num', INT4),
1306
+ ('radial_num', INT4),
1307
+ ('ele_num', INT4),
1308
+ ('azimuth', REAL4),
1309
+ ('elevation', REAL4),
1310
+ ('seconds', INT4),
1311
+ ('micro_seconds', INT4),
1312
+ ('len_data', INT4),
1313
+ ('mom_num', INT4),
1314
+ ('reserved', '20s')
1315
+ )
1316
+
1317
+ # 表3-2
1318
+ RADIAL_HEAD = (
1319
+ ('data_type', INT4),
1320
+ ('scale', INT4),
1321
+ ('offset', INT4),
1322
+ ('bin_len', INT2),
1323
+ ('flags', INT2),
1324
+ ('length', INT4),
1325
+ ('reserved', '12s')
1326
+ )