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.
- metradar/__init__.py +7 -0
- metradar/cnrad_level2.py +1326 -0
- metradar/comm_func.py +135 -0
- metradar/construct_aws_refvpr_mainprog.py +515 -0
- metradar/construct_aws_refvpr_mainprog_cams.py +310 -0
- metradar/construct_aws_refvpr_mainprog_datan3d.py +386 -0
- metradar/construct_aws_refvpr_mainprog_swan.py +306 -0
- metradar/decode_fmt_pyart.py +200 -0
- metradar/decode_pup_rose.py +1993 -0
- metradar/draw_mosaic_new.py +421 -0
- metradar/draw_radar_aws_jilin_new.py +206 -0
- metradar/draw_radar_comp_func.py +1379 -0
- metradar/exceptions.py +50 -0
- metradar/geo_transforms_pyart.py +627 -0
- metradar/get_cross_section_from_pyart.py +354 -0
- metradar/get_tlogp_from_sharppy.py +93 -0
- metradar/grid.py +281 -0
- metradar/grid_data.py +64 -0
- metradar/main_pydda.py +653 -0
- metradar/make_gif.py +24 -0
- metradar/make_mosaic_mp_archive.py +538 -0
- metradar/mosaic_merge.py +64 -0
- metradar/mosaic_quickdraw.py +338 -0
- metradar/nowcast_by_pysteps.py +219 -0
- metradar/oa_couhua.py +166 -0
- metradar/oa_dig_func.py +955 -0
- metradar/parse_pal.py +148 -0
- metradar/pgmb_io.py +169 -0
- metradar/prepare_for_radar_draw.py +197 -0
- metradar/read_new_mosaic.py +33 -0
- metradar/read_new_mosaic_func.py +231 -0
- metradar/retrieve_cmadaas.py +3126 -0
- metradar/retrieve_micaps_server.py +2061 -0
- metradar/rose_structer.py +807 -0
- metradar/trans_nc_pgmb.py +62 -0
- metradar/trans_new_mosaic_nc.py +309 -0
- metradar/trans_polor2grid_func.py +203 -0
- metradar-0.1.0.dist-info/METADATA +12 -0
- metradar-0.1.0.dist-info/RECORD +41 -0
- metradar-0.1.0.dist-info/WHEEL +5 -0
- metradar-0.1.0.dist-info/top_level.txt +1 -0
metradar/cnrad_level2.py
ADDED
|
@@ -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
|
+
)
|