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,1993 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ '''
4
+ @File : decode_pup_rose.py
5
+ @Time : 2023/04/27 23:12:54
6
+ @Author : Wenjian Zhu
7
+ @Version : 1.0
8
+ @Email : kevin2075@163.com
9
+ '''
10
+
11
+ '''
12
+ 解析pup_rose文件
13
+
14
+ '''
15
+
16
+ # %%
17
+ import os
18
+ import numpy as np
19
+ import geo_transforms_pyart as geotrans
20
+ from datetime import datetime,timedelta
21
+ # from rose_structer import _unpack_from_buf,_structure_size,SIT_HEADER_BLOCK,GENERIC_HEADER,\
22
+ # SITE_CONFIG,TASK_CONFIG,SCAN_CONFIG,PRODUCT_HEADER_BLOCK,PRODUCT_ID_NAME_MAP,\
23
+ # PRODUCT_DEPENDENT_PARAMETER,STORM_MOTION_BLOCK,STORM_FST_HIS_NUM,STORM_FST_HIS_BLOCK, \
24
+ # STORM_PROPERTY,STORM_TRACK_PARAM,STORM_COMPONENT
25
+ import xarray as xr
26
+ import rose_structer as rs
27
+
28
+ class READ_ROSE(object):
29
+
30
+
31
+ def __init__(self,):
32
+ self.stiinfo = None
33
+ self.mesoinfo = None
34
+ self.hailinfo = None
35
+ self.tvsinfo = None
36
+ self.ssinfo = None
37
+ pass
38
+
39
+ # 将数字风暴序号转为字母数字组合
40
+
41
+ def get_id_char(self,id_num):
42
+ '''
43
+ 该函数的算法由张持岸提供
44
+ '''
45
+ if not isinstance(id_num,int):
46
+ print('id_num is not int')
47
+ return None
48
+ if id_num > 0:
49
+ num = (id_num-1) // 26
50
+ tail = (id_num-1) % 26
51
+ newid = '%s%d'%(chr(65+tail),num)
52
+ else:
53
+ newid = '0'
54
+ return newid
55
+
56
+ #将数字的风向转换为文字
57
+ def get_wind_dir_name(self,wdir):
58
+ wdir_cn = '未知'
59
+ if not isinstance(wdir,int) and not isinstance(wdir,float):
60
+ return wdir_cn
61
+ if (wdir >= 348.76 and wdir <= 360) or (wdir >= 0 and wdir <= 11.25):
62
+ wdir_cn = '北'
63
+ elif wdir >= 11.26 and wdir <=33.75:
64
+ wdir_cn = '北东北'
65
+ elif wdir >= 33.76 and wdir <=56.25:
66
+ wdir_cn = '东北'
67
+ elif wdir >= 56.26 and wdir <=78.75:
68
+ wdir_cn = '东东北'
69
+ elif wdir >= 78.76 and wdir <101.25:
70
+ wdir_cn = '东'
71
+ elif wdir >= 101.26 and wdir <123.75:
72
+ wdir_cn = '东东南'
73
+ elif wdir >= 123.76 and wdir <146.25:
74
+ wdir_cn = '东南'
75
+ elif wdir >= 146.26 and wdir <168.75:
76
+ wdir_cn = '南东南'
77
+ elif wdir >= 168.76 and wdir <191.25:
78
+ wdir_cn = '南'
79
+ elif wdir >= 191.26 and wdir <213.75:
80
+ wdir_cn = '南西南'
81
+ elif wdir >= 213.76 and wdir <236.25:
82
+ wdir_cn = '西南'
83
+ elif wdir >= 236.26 and wdir <258.75:
84
+ wdir_cn = '西西南'
85
+ elif wdir >= 258.76 and wdir <281.25:
86
+ wdir_cn = '西'
87
+ elif wdir >= 281.26 and wdir <303.75:
88
+ wdir_cn = '西西北'
89
+ elif wdir >= 303.76 and wdir <326.25:
90
+ wdir_cn = '西北'
91
+ elif wdir >= 326.26 and wdir <348.75:
92
+ wdir_cn = '北西北'
93
+
94
+ return wdir_cn
95
+
96
+ def get_mda_rank(self,range_from_radar,shear_value):
97
+
98
+ '''
99
+ range_from_radar: km
100
+ shear_value : m/s
101
+ '''
102
+ knot2ms = 0.514444445
103
+ nmi = 1.852
104
+
105
+ #x = np.array([0,30,60,90,120,150,180,210]) # x取值
106
+ x = np.arange(0,250) # x取值
107
+ y1 = -1.*knot2ms*x/(24*nmi)+25*knot2ms
108
+ y2 = -1.*knot2ms*x/(18*nmi)+35*knot2ms
109
+ y3 = -1.*knot2ms*x/(14*nmi)+45*knot2ms
110
+
111
+ curx = int(range_from_radar)
112
+ if curx >=250 or curx <=0:
113
+ print('range from radar should be larger than 0 and less than 250km ')
114
+ return None
115
+
116
+ cury = shear_value
117
+ if cury < y1[curx]:
118
+ return 1
119
+ elif cury <y2[curx]:
120
+ return 2
121
+ elif cury < y3[curx]:
122
+ return 3
123
+ else:
124
+ return 4
125
+
126
+ def get_tvs_rank(self,lldv_value):
127
+
128
+ # the "19" icon indicates that LLDV was at least 190 kts.
129
+
130
+ ms2knot = 1.943844490
131
+
132
+ value = lldv_value * ms2knot
133
+
134
+ index = value // 10
135
+ if index > 19:
136
+ index=19
137
+
138
+ return index+1
139
+
140
+ # 解析风暴追踪产品
141
+ def read_sti(self,filepath,filename):
142
+ '''
143
+ 解析sti产品文件
144
+ '''
145
+ fin = open(filepath + os.sep + filename,'rb')
146
+ buf = fin.read()
147
+ fin.close()
148
+ buf_length = len(buf)
149
+ pos = 0
150
+
151
+ # 获取通用信息头
152
+ dic_gh = rs._unpack_from_buf(buf, pos, rs.GENERIC_HEADER)
153
+ # pprint(dic_gh)
154
+ if dic_gh['magic_number'] != 0x4D545352:
155
+ print('源数据格式错误!')
156
+ pos = pos + rs._structure_size(rs.GENERIC_HEADER)
157
+
158
+ # 获取站点信息
159
+ dic_scfg = rs._unpack_from_buf(buf, pos, rs.SITE_CONFIG)
160
+ pos = pos + rs._structure_size(rs.SITE_CONFIG)
161
+
162
+ # 获取任务信息
163
+ dic_tcfg = rs._unpack_from_buf(buf, pos, rs.TASK_CONFIG)
164
+ pos = pos + rs._structure_size(rs.TASK_CONFIG)
165
+
166
+ # 获取扫描信息
167
+ cutinfo = []
168
+ for im in np.arange(dic_tcfg['cut_number']):
169
+ dic_cutcfg = rs._unpack_from_buf(buf, pos, rs.SCAN_CONFIG)
170
+ cutinfo.append(dic_cutcfg)
171
+ pos = pos + rs._structure_size(rs.SCAN_CONFIG)
172
+
173
+ # 获取产品头信息
174
+ dic_prod_header = rs._unpack_from_buf(buf, pos, rs.PRODUCT_HEADER_BLOCK)
175
+ pos = pos + rs._structure_size(rs.PRODUCT_HEADER_BLOCK)
176
+
177
+ # 获取产品参数信息
178
+ prod_type = rs.PRODUCT_ID_NAME_MAP[dic_prod_header['product_type']]
179
+ # pprint(prod_type)
180
+ dic_prod_param = rs._unpack_from_buf(buf, pos, rs.PRODUCT_DEPENDENT_PARAMETER[prod_type])
181
+ pos = pos + 64 # 产品参数长度固定为64个
182
+
183
+
184
+ # 获取产品数据块
185
+
186
+ #获取STI头信息
187
+ dic_sti_header = rs._unpack_from_buf(buf, pos, rs.SIT_HEADER_BLOCK)
188
+ pos = pos + rs._structure_size(rs.SIT_HEADER_BLOCK)
189
+
190
+ # 风暴追踪信息块
191
+ # 风暴移动信息
192
+ storm_motion_block=[]
193
+ for nn in np.arange(dic_sti_header['storm_number']):
194
+ storm_motion_block.append(rs._unpack_from_buf(buf, pos, rs.STORM_MOTION_BLOCK))
195
+ pos = pos + rs._structure_size(rs.STORM_MOTION_BLOCK)
196
+
197
+ # 风暴预报信息
198
+ all_storm_fst_block=[]
199
+ for nn in np.arange(dic_sti_header['storm_number']):
200
+ dic_storm_fst_num = rs._unpack_from_buf(buf, pos, rs.STORM_FST_HIS_NUM)
201
+ pos = pos + rs._structure_size(rs.STORM_FST_HIS_NUM)
202
+
203
+ storm_fst_block=[]
204
+ for nn in np.arange(dic_storm_fst_num['position_number']):
205
+ storm_fst_block.append(rs._unpack_from_buf(buf, pos, rs.STORM_FST_HIS_BLOCK))
206
+ pos = pos + rs._structure_size(rs.STORM_FST_HIS_BLOCK)
207
+ all_storm_fst_block.append(storm_fst_block)
208
+
209
+ # 风暴历史信息
210
+ all_storm_his_block=[]
211
+ for nn in np.arange(dic_sti_header['storm_number']):
212
+ dic_storm_his_num = rs._unpack_from_buf(buf, pos, rs.STORM_FST_HIS_NUM)
213
+ pos = pos + rs._structure_size(rs.STORM_FST_HIS_NUM)
214
+
215
+ storm_his_block=[]
216
+ for nn in np.arange(dic_storm_his_num['position_number']):
217
+ storm_his_block.append(rs._unpack_from_buf(buf, pos, rs.STORM_FST_HIS_BLOCK))
218
+ pos = pos + rs._structure_size(rs.STORM_FST_HIS_BLOCK)
219
+ all_storm_his_block.append(storm_his_block)
220
+
221
+ # 风暴属性表块数据
222
+ # 风暴属性
223
+ all_storm_prop=[]
224
+ for nn in np.arange(dic_sti_header['storm_number']):
225
+ dic_storm_prop = rs._unpack_from_buf(buf, pos, rs.STORM_PROPERTY)
226
+ pos = pos + rs._structure_size(rs.STORM_PROPERTY)
227
+
228
+ # 将风暴数字ID转换为字符串ID
229
+ dic_storm_prop['id_char'] = self.get_id_char(dic_storm_prop['id'])
230
+
231
+ # 将风暴移动信息融合进来
232
+ dic_storm_prop['mv_spd'] = storm_motion_block[nn]['mv_spd']
233
+ dic_storm_prop['mv_dir'] = storm_motion_block[nn]['mv_dir']
234
+
235
+ all_storm_prop.append(dic_storm_prop)
236
+
237
+ # 风暴构成表
238
+ all_storm_comp=[]
239
+ for nn in np.arange(dic_sti_header['storm_number']):
240
+ dic_storm_comp = rs._unpack_from_buf(buf, pos, rs.STORM_COMPONENT)
241
+ pos = pos + rs._structure_size(rs.STORM_COMPONENT)
242
+ all_storm_comp.append(dic_storm_comp)
243
+
244
+ # 风暴追踪适配数据
245
+ dic_storm_track_param = rs._unpack_from_buf(buf, pos, rs.STORM_TRACK_PARAM)
246
+ pos = pos + rs._structure_size(rs.STORM_TRACK_PARAM)
247
+
248
+
249
+ allresult={}
250
+ allresult['dic_gh'] = dic_gh
251
+ allresult['dic_scfg'] = dic_scfg
252
+ allresult['dic_tcfg'] = dic_tcfg
253
+ allresult['cutinfo'] = cutinfo
254
+ allresult['dic_prod_header'] = dic_prod_header
255
+ allresult['dic_prod_param'] = dic_prod_param
256
+ allresult['dic_sti_header'] = dic_sti_header
257
+ allresult['storm_motion_block'] = storm_motion_block
258
+ allresult['all_storm_fst_block'] = all_storm_fst_block
259
+ allresult['all_storm_his_block'] = all_storm_his_block
260
+ allresult['all_storm_prop'] = all_storm_prop
261
+ allresult['all_storm_comp'] = all_storm_comp
262
+ allresult['dic_storm_track_param'] = dic_storm_track_param
263
+
264
+ # 将所有的方位角和距离转换成经纬度
265
+ for nn in np.arange(dic_sti_header['storm_number']):
266
+ pass
267
+ x,y,z = geotrans.antenna_to_cartesian(storm_motion_block[nn]['range']/1000.0,storm_motion_block[nn]['azi'],0)
268
+ clon,clat = geotrans.cartesian_to_geographic_aeqd(x,y,dic_scfg['lon'],dic_scfg['lat'])
269
+ storm_motion_block[nn]['lon'] = clon[0]
270
+ storm_motion_block[nn]['lat'] = clat[0]
271
+ all_storm_prop[nn]['lon'] = clon[0]
272
+ all_storm_prop[nn]['lat'] = clat[0]
273
+ for blk in all_storm_fst_block[nn]:
274
+ x,y,z = geotrans.antenna_to_cartesian(blk['range']/1000.0,blk['azi'],0)
275
+ clon,clat = geotrans.cartesian_to_geographic_aeqd(x,y,dic_scfg['lon'],dic_scfg['lat'])
276
+ blk['lon'] = clon[0]
277
+ blk['lat'] = clat[0]
278
+ for blk in all_storm_his_block[nn]:
279
+ x,y,z = geotrans.antenna_to_cartesian(blk['range']/1000.0,blk['azi'],0)
280
+ clon,clat = geotrans.cartesian_to_geographic_aeqd(x,y,dic_scfg['lon'],dic_scfg['lat'])
281
+ blk['lon'] = clon[0]
282
+ blk['lat'] = clat[0]
283
+
284
+ pass
285
+
286
+ # 整理成track
287
+ alltrackinfo=[]
288
+ all_past_position=[] # 历史位置
289
+ all_fst_position=[] # 预报位置
290
+ all_current_position=[] # 当前位置
291
+ for nn in np.arange(dic_sti_header['storm_number']):
292
+ pass
293
+
294
+ if all_storm_prop[nn]['type']==0: # 连续风暴
295
+ trackinfo=[]
296
+
297
+ # 先添加历史位置
298
+ for blk in all_storm_his_block[nn][::-1]:
299
+ trackinfo.append([blk['lat'],blk['lon']])
300
+ all_past_position.append([blk['lat'],blk['lon']])
301
+
302
+ # 添加当前风暴位置
303
+ trackinfo.append([storm_motion_block[nn]['lat'],storm_motion_block[nn]['lon']])
304
+ all_current_position.append([storm_motion_block[nn]['lat'],storm_motion_block[nn]['lon']])
305
+
306
+ # 再添加预报位置
307
+ for blk in all_storm_fst_block[nn]:
308
+ trackinfo.append([blk['lat'],blk['lon']])
309
+ all_fst_position.append([blk['lat'],blk['lon']])
310
+
311
+ alltrackinfo.append(trackinfo)
312
+
313
+ # allresult={}
314
+ allresult['track'] = alltrackinfo
315
+ allresult['marker_past'] = all_past_position
316
+ allresult['marker_current'] = all_current_position
317
+ allresult['marker_fst'] = all_fst_position
318
+
319
+ self.stiinfo = allresult
320
+ return allresult
321
+
322
+ # 解析风暴结构文本产品
323
+ def read_ss(self,filepath,filename):
324
+ '''
325
+ 解析ss产品文件
326
+ '''
327
+ fin = open(filepath + os.sep + filename,'rb')
328
+ buf = fin.read()
329
+ fin.close()
330
+ buf_length = len(buf)
331
+ pos = 0
332
+
333
+ # 获取通用信息头
334
+ dic_gh = rs._unpack_from_buf(buf, pos, rs.GENERIC_HEADER)
335
+ # pprint(dic_gh)
336
+ if dic_gh['magic_number'] != 0x4D545352:
337
+ print('源数据格式错误!')
338
+ pos = pos + rs._structure_size(rs.GENERIC_HEADER)
339
+
340
+ # 获取站点信息
341
+ dic_scfg = rs._unpack_from_buf(buf, pos, rs.SITE_CONFIG)
342
+ pos = pos + rs._structure_size(rs.SITE_CONFIG)
343
+
344
+ # 获取任务信息
345
+ dic_tcfg = rs._unpack_from_buf(buf, pos, rs.TASK_CONFIG)
346
+ pos = pos + rs._structure_size(rs.TASK_CONFIG)
347
+
348
+ # 获取扫描信息
349
+ cutinfo = []
350
+ for im in np.arange(dic_tcfg['cut_number']):
351
+ dic_cutcfg = rs._unpack_from_buf(buf, pos, rs.SCAN_CONFIG)
352
+ cutinfo.append(dic_cutcfg)
353
+ pos = pos + rs._structure_size(rs.SCAN_CONFIG)
354
+
355
+ # 获取产品头信息
356
+ dic_prod_header = rs._unpack_from_buf(buf, pos, rs.PRODUCT_HEADER_BLOCK)
357
+ pos = pos + rs._structure_size(rs.PRODUCT_HEADER_BLOCK)
358
+
359
+ # 获取产品参数信息
360
+ prod_type = rs.PRODUCT_ID_NAME_MAP[dic_prod_header['product_type']]
361
+ # pprint(prod_type)
362
+ dic_prod_param = rs._unpack_from_buf(buf, pos, rs.PRODUCT_DEPENDENT_PARAMETER[prod_type])
363
+ pos = pos + 64 # 产品参数长度固定为64个
364
+
365
+
366
+ # 获取产品数据块
367
+
368
+ #获取SS头信息
369
+ dic_ss_header = rs._unpack_from_buf(buf, pos, rs.SS_HEAD_BLOCK)
370
+ pos = pos + rs._structure_size(rs.SS_HEAD_BLOCK)
371
+
372
+ # SS结构信息
373
+ ss_tab=[]
374
+ for nn in np.arange(dic_ss_header['storm_number']):
375
+ tmp_tab = rs._unpack_from_buf(buf, pos, rs.SS_TAB)
376
+ pos = pos + rs._structure_size(rs.SS_TAB)
377
+ # 将风暴数字ID转换为字符串ID
378
+ tmp_tab['id_char'] = self.get_id_char(tmp_tab['storm_id'])
379
+
380
+ ss_tab.append(tmp_tab)
381
+
382
+
383
+ # 风暴趋势信息
384
+ cell_trend=[]
385
+ for nn in np.arange(dic_ss_header['storm_number']):
386
+ cur_cellinfo={}
387
+ cell_info=rs._unpack_from_buf(buf, pos, rs.CELL_TREND)
388
+ pos = pos + rs._structure_size(rs.CELL_TREND)
389
+ cur_cellinfo['head_info'] = cell_info
390
+ # 历史体扫信息
391
+ tmpcell_his=[]
392
+ for im in np.arange(cell_info['his_vol_num']):
393
+ tmpcell_his.append(rs._unpack_from_buf(buf, pos, rs.HIS_VOL))
394
+ pos = pos + rs._structure_size(rs.HIS_VOL)
395
+ cur_cellinfo['cell_info'] = tmpcell_his
396
+
397
+ cell_trend.append(cur_cellinfo)
398
+
399
+ # 风暴段适配数据
400
+ seg_adapt = rs._unpack_from_buf(buf, pos, rs.SEG_ADAPT)
401
+ pos = pos + rs._structure_size(rs.SEG_ADAPT)
402
+
403
+ # 风暴质心适配数据
404
+ centroid_adapt = rs._unpack_from_buf(buf, pos, rs.CENTROIDS_ADAPT)
405
+ pos = pos + rs._structure_size(rs.CENTROIDS_ADAPT)
406
+
407
+ # 风暴追踪适配数据
408
+ storm_track_adapt = rs._unpack_from_buf(buf, pos, rs.STORM_TRACK_PARAM)
409
+ pos = pos + rs._structure_size(rs.STORM_TRACK_PARAM)
410
+
411
+
412
+ # 将所有的方位角和距离转换成经纬度
413
+ for nn in np.arange(dic_ss_header['storm_number']):
414
+ pass
415
+ x,y,z = geotrans.antenna_to_cartesian(ss_tab[nn]['range']/1000.0,ss_tab[nn]['azi'],0)
416
+ clon,clat = geotrans.cartesian_to_geographic_aeqd(x,y,dic_scfg['lon'],dic_scfg['lat'])
417
+ ss_tab[nn]['lon'] = clon[0]
418
+ ss_tab[nn]['lat'] = clat[0]
419
+
420
+
421
+ allresult={}
422
+ allresult['ss'] = ss_tab
423
+
424
+ self.ssinfo = allresult
425
+ return allresult
426
+
427
+ # 解析中气旋产品
428
+ def read_mda(self,filepath,filename):
429
+ '''
430
+ 解析meso产品文件
431
+ '''
432
+ fin = open(filepath + os.sep + filename,'rb')
433
+ buf = fin.read()
434
+ fin.close()
435
+ buf_length = len(buf)
436
+ pos = 0
437
+
438
+ # 获取通用信息头
439
+ dic_gh = rs._unpack_from_buf(buf, pos, rs.GENERIC_HEADER)
440
+ # pprint(dic_gh)
441
+ if dic_gh['magic_number'] != 0x4D545352:
442
+ print('源数据格式错误!')
443
+ pos = pos + rs._structure_size(rs.GENERIC_HEADER)
444
+
445
+ # 获取站点信息
446
+ dic_scfg = rs._unpack_from_buf(buf, pos, rs.SITE_CONFIG)
447
+ pos = pos + rs._structure_size(rs.SITE_CONFIG)
448
+
449
+ # 获取任务信息
450
+ dic_tcfg = rs._unpack_from_buf(buf, pos, rs.TASK_CONFIG)
451
+ pos = pos + rs._structure_size(rs.TASK_CONFIG)
452
+
453
+ # 获取扫描信息
454
+ cutinfo = []
455
+ for im in np.arange(dic_tcfg['cut_number']):
456
+ dic_cutcfg = rs._unpack_from_buf(buf, pos, rs.SCAN_CONFIG)
457
+ cutinfo.append(dic_cutcfg)
458
+ pos = pos + rs._structure_size(rs.SCAN_CONFIG)
459
+
460
+ # 获取产品头信息
461
+ dic_prod_header = rs._unpack_from_buf(buf, pos, rs.PRODUCT_HEADER_BLOCK)
462
+ pos = pos + rs._structure_size(rs.PRODUCT_HEADER_BLOCK)
463
+
464
+ # 获取产品参数信息
465
+ prod_type = rs.PRODUCT_ID_NAME_MAP[dic_prod_header['product_type']]
466
+ # pprint(prod_type)
467
+ dic_prod_param = rs._unpack_from_buf(buf, pos, rs.PRODUCT_DEPENDENT_PARAMETER[prod_type])
468
+ pos = pos + 64 # 产品参数长度固定为64个
469
+
470
+ # 获取产品数据块
471
+
472
+ #获取中气旋头信息
473
+ dic_meso_header = rs._unpack_from_buf(buf, pos, rs.MESO_HEADER_BLOCK)
474
+ pos = pos + rs._structure_size(rs.MESO_HEADER_BLOCK)
475
+
476
+ # 中气旋表块
477
+ meso_tab=[]
478
+ for nn in np.arange(dic_meso_header['meso_number']):
479
+ meso_tab.append(rs._unpack_from_buf(buf, pos, rs.MESO_TABLE))
480
+ pos = pos + rs._structure_size(rs.MESO_TABLE)
481
+
482
+ # 中气旋特征表
483
+ meso_feature_tab=[]
484
+ for nn in np.arange(dic_meso_header['feature_number']):
485
+ meso_feature_tab.append(rs._unpack_from_buf(buf, pos, rs.MESO_FEATURE_TAB))
486
+ pos = pos + rs._structure_size(rs.MESO_FEATURE_TAB)
487
+
488
+ # 中气旋适配数据
489
+ dic_meso_adapt_param = rs._unpack_from_buf(buf, pos, rs.MESO_ADAPTATION_DATA)
490
+ pos = pos + rs._structure_size(rs.MESO_ADAPTATION_DATA)
491
+
492
+
493
+
494
+ for nn in np.arange(dic_meso_header['meso_number']):
495
+ # 将所有的方位角和距离转换成经纬度
496
+ x,y,z = geotrans.antenna_to_cartesian(meso_tab[nn]['range']/1000.0,meso_tab[nn]['azi'],0)
497
+ clon,clat = geotrans.cartesian_to_geographic_aeqd(x,y,dic_scfg['lon'],dic_scfg['lat'])
498
+ meso_tab[nn]['lon'] = clon[0]
499
+ meso_tab[nn]['lat'] = clat[0]
500
+
501
+ # 将数字序号转换成字母数字组合
502
+ meso_tab[nn]['storm_id_char'] = self.get_id_char(meso_tab[nn]['storm_id'])
503
+ meso_tab[nn]['feature_id_char'] = self.get_id_char(meso_tab[nn]['feature_id'])
504
+ meso_tab[nn]['feature_type'] = meso_feature_tab[nn]['feature_type']
505
+
506
+ for nn in np.arange(dic_meso_header['feature_number']):
507
+ # 将所有的方位角和距离转换成经纬度
508
+ x,y,z = geotrans.antenna_to_cartesian(meso_feature_tab[nn]['range']/1000.0,meso_feature_tab[nn]['azi'],0)
509
+ clon,clat = geotrans.cartesian_to_geographic_aeqd(x,y,dic_scfg['lon'],dic_scfg['lat'])
510
+ meso_feature_tab[nn]['lon'] = clon[0]
511
+ meso_feature_tab[nn]['lat'] = clat[0]
512
+
513
+ # 将数字序号转换成字母数字组合
514
+ meso_feature_tab[nn]['storm_id_char'] = self.get_id_char(meso_feature_tab[nn]['storm_id'])
515
+ meso_feature_tab[nn]['feature_id_char'] = self.get_id_char(meso_feature_tab[nn]['feature_id'])
516
+
517
+
518
+ allresult={}
519
+ allresult['meso'] = meso_tab
520
+ allresult['feature'] = meso_feature_tab
521
+
522
+ self.mesoinfo = allresult
523
+ return allresult
524
+
525
+ # 解析龙卷涡旋特征产品
526
+ def read_tvs(self,filepath,filename):
527
+ '''
528
+ 解析tvs产品文件
529
+ '''
530
+ fin = open(filepath + os.sep + filename,'rb')
531
+ buf = fin.read()
532
+ fin.close()
533
+ buf_length = len(buf)
534
+ pos = 0
535
+
536
+ # 获取通用信息头
537
+ dic_gh = rs._unpack_from_buf(buf, pos, rs.GENERIC_HEADER)
538
+ # pprint(dic_gh)
539
+ if dic_gh['magic_number'] != 0x4D545352:
540
+ print('源数据格式错误!')
541
+ pos = pos + rs._structure_size(rs.GENERIC_HEADER)
542
+
543
+ # 获取站点信息
544
+ dic_scfg = rs._unpack_from_buf(buf, pos, rs.SITE_CONFIG)
545
+ pos = pos + rs._structure_size(rs.SITE_CONFIG)
546
+
547
+ # 获取任务信息
548
+ dic_tcfg = rs._unpack_from_buf(buf, pos, rs.TASK_CONFIG)
549
+ pos = pos + rs._structure_size(rs.TASK_CONFIG)
550
+
551
+ # 获取扫描信息
552
+ cutinfo = []
553
+ for im in np.arange(dic_tcfg['cut_number']):
554
+ dic_cutcfg = rs._unpack_from_buf(buf, pos, rs.SCAN_CONFIG)
555
+ cutinfo.append(dic_cutcfg)
556
+ pos = pos + rs._structure_size(rs.SCAN_CONFIG)
557
+
558
+ # 获取产品头信息
559
+ dic_prod_header = rs._unpack_from_buf(buf, pos, rs.PRODUCT_HEADER_BLOCK)
560
+ pos = pos + rs._structure_size(rs.PRODUCT_HEADER_BLOCK)
561
+
562
+ # 获取产品参数信息
563
+ prod_type = rs.PRODUCT_ID_NAME_MAP[dic_prod_header['product_type']]
564
+ # pprint(prod_type)
565
+ dic_prod_param = rs._unpack_from_buf(buf, pos, rs.PRODUCT_DEPENDENT_PARAMETER[prod_type])
566
+ pos = pos + 64 # 产品参数长度固定为64个
567
+
568
+ # 获取产品数据块
569
+
570
+ #获取TVS头信息
571
+ dic_tvs_header = rs._unpack_from_buf(buf, pos, rs.TVS_HEADER_BLOCK)
572
+ pos = pos + rs._structure_size(rs.TVS_HEADER_BLOCK)
573
+
574
+ # TVS表块
575
+ tvs_tab=[]
576
+ for nn in np.arange(dic_tvs_header['tvs_number']):
577
+ tvs_tab.append(rs._unpack_from_buf(buf, pos, rs.TVS_TAB))
578
+ pos = pos + rs._structure_size(rs.TVS_TAB)
579
+
580
+ # TVS适配数据
581
+ dic_tvs_adapt_param = rs._unpack_from_buf(buf, pos, rs.TVS_ADAPTATION_DATA)
582
+ pos = pos + rs._structure_size(rs.TVS_ADAPTATION_DATA)
583
+
584
+
585
+ # 将所有的方位角和距离转换成经纬度
586
+ for nn in np.arange(dic_tvs_header['tvs_number']):
587
+ pass
588
+ x,y,z = geotrans.antenna_to_cartesian(tvs_tab[nn]['range']/1000.0,tvs_tab[nn]['azi'],0)
589
+ clon,clat = geotrans.cartesian_to_geographic_aeqd(x,y,dic_scfg['lon'],dic_scfg['lat'])
590
+ tvs_tab[nn]['lon'] = clon[0]
591
+ tvs_tab[nn]['lat'] = clat[0]
592
+
593
+
594
+ # 将数字序号转换成字母数字组合
595
+ tvs_tab[nn]['storm_id_char'] = self.get_id_char(tvs_tab[nn]['storm_id'])
596
+
597
+
598
+
599
+ allresult={}
600
+ allresult['tvs'] = tvs_tab
601
+
602
+ self.tvsinfo = allresult
603
+ return allresult
604
+
605
+ # 解析ppi产品文件
606
+ def read_ppi(self,filepath,filename):
607
+ '''
608
+ 解析ppi产品文件
609
+ '''
610
+ pass
611
+
612
+ fin = open(filepath + os.sep + filename,'rb')
613
+ buf = fin.read()
614
+ fin.close()
615
+ buf_length = len(buf)
616
+ pos = 0
617
+
618
+ # 获取通用信息头
619
+ dic_gh = rs._unpack_from_buf(buf, pos, rs.GENERIC_HEADER)
620
+ # pprint(dic_gh)
621
+ if dic_gh['magic_number'] != 0x4D545352:
622
+ print('源数据格式错误!')
623
+ return None
624
+ pos = pos + rs._structure_size(rs.GENERIC_HEADER)
625
+
626
+ # 获取站点信息
627
+ dic_scfg = rs._unpack_from_buf(buf, pos, rs.SITE_CONFIG)
628
+ pos = pos + rs._structure_size(rs.SITE_CONFIG)
629
+
630
+ # 获取任务信息
631
+ dic_tcfg = rs._unpack_from_buf(buf, pos, rs.TASK_CONFIG)
632
+ pos = pos + rs._structure_size(rs.TASK_CONFIG)
633
+
634
+ # 获取扫描信息
635
+ cutinfo = []
636
+ for im in np.arange(dic_tcfg['cut_number']):
637
+ dic_cutcfg = rs._unpack_from_buf(buf, pos, rs.SCAN_CONFIG)
638
+ cutinfo.append(dic_cutcfg)
639
+ pos = pos + rs._structure_size(rs.SCAN_CONFIG)
640
+
641
+ # 获取产品头信息
642
+ dic_prod_header = rs._unpack_from_buf(buf, pos, rs.PRODUCT_HEADER_BLOCK)
643
+ pos = pos + rs._structure_size(rs.PRODUCT_HEADER_BLOCK)
644
+
645
+ # 获取产品参数信息
646
+ prod_type = rs.PRODUCT_ID_NAME_MAP[dic_prod_header['product_type']]
647
+ dic_prod_param = rs._unpack_from_buf(buf, pos, rs.PRODUCT_DEPENDENT_PARAMETER[prod_type])
648
+ pos = pos + 64 # 产品参数长度固定为64个
649
+
650
+ # 读取产品径向头信息
651
+ dic_radial_header = rs._unpack_from_buf(buf, pos, rs.RADIAL_HEADER)
652
+ pos = pos + rs._structure_size(rs.RADIAL_HEADER)
653
+
654
+ print('数据类型: %d-%s'%(dic_radial_header['data_type'],rs.PRODUCT_DATA_TYPE[dic_radial_header['data_type']]))
655
+ # Value=(Code-Offset)/Scale
656
+ # 读取径向数据RADIAL_DATA
657
+ data=[]
658
+ data_azi=[]
659
+ binnum = None
660
+ for nn in np.arange(dic_radial_header['radial_number']):
661
+ radial_data_info = rs._unpack_from_buf(buf, pos, rs.RADIAL_DATA)
662
+ pos = pos + rs._structure_size(rs.RADIAL_DATA)
663
+ binnum = radial_data_info['num_bins']
664
+ rdata = buf[pos:pos+dic_radial_header['bin_length']*radial_data_info['num_bins']]
665
+ data.extend(rdata)
666
+ pos = pos + dic_radial_header['bin_length']*radial_data_info['num_bins']
667
+ # print(radial_data_info['start_azi'])
668
+ data_azi.append(radial_data_info['start_azi'])
669
+ pass
670
+ data = np.array(data)
671
+
672
+
673
+ # data = data.astype('uint8')
674
+ data = data.astype('float32')
675
+ nx = dic_radial_header['radial_number']
676
+ ny = binnum
677
+ print(nx,ny)
678
+ # 将data转成numpy数组
679
+ data = data.reshape((nx,ny))
680
+
681
+ # decode data
682
+ # ppiarray = (ppiarray - dic_radial_header['offset']) / dic_radial_header['scale']
683
+
684
+
685
+ # 将方位角坐标转换成经纬度坐标
686
+ sweep_azimuths =np.array(data_azi)
687
+ ele = dic_prod_param['ele']
688
+ range_reso = dic_radial_header['resolution']
689
+ # 输出的距离库只保留一半
690
+ ngates = int(ny//2*2) #//2*2
691
+ grid_x = np.arange(-1*ngates,ngates+1,1)
692
+ grid_y = np.arange(-1*ngates,ngates+1,1)
693
+ total_gates = len(grid_x)
694
+
695
+ aa = np.meshgrid(grid_x,grid_y)
696
+ azi_grid = np.arctan2(aa[0],aa[1])*180/np.pi
697
+ azi_grid[azi_grid<0]+=360
698
+
699
+ azi_reso = 360/len(sweep_azimuths)
700
+
701
+ # 求方位角索引
702
+ new_azi = azi_grid.flatten()
703
+ t = new_azi-sweep_azimuths[0]
704
+ t[t<0]+=360
705
+ ray_number = np.round(t/azi_reso,0).astype(int)
706
+ ray_number[ray_number==len(sweep_azimuths)]=0
707
+ ray_number = np.reshape(ray_number,(total_gates,total_gates))
708
+
709
+ # 求距离索引
710
+ dis_grid = np.sqrt(aa[0]**2 + aa[1]**2)
711
+ dis_grid = np.round(dis_grid.flatten(),0).astype(int)
712
+ dis_grid = np.reshape(dis_grid,(total_gates,total_gates))
713
+
714
+ # 对数据进行截断,在径向方向上
715
+ data = data[:,0:ngates]
716
+
717
+ # data_grid = np.zeros((total_gates,total_gates),dtype='uint8') + 255
718
+ data_grid = np.zeros((total_gates,total_gates),dtype='float32')
719
+ new_data = data_grid.flatten()
720
+ new_spdata = data.flatten()
721
+
722
+ pos_out = [i+j*total_gates for i in range(total_gates) for j in range(total_gates) if dis_grid[i,j] < ngates]
723
+ d_out = [rn*ngates+dg for rn,dg in zip(ray_number.flatten(),dis_grid.flatten()) if dg < ngates]
724
+
725
+ new_data[pos_out]=new_spdata[d_out]
726
+
727
+ data_grid = np.reshape(new_data,(total_gates,total_gates))
728
+
729
+
730
+ if total_gates % 2 == 0:
731
+ out_grid = np.arange(int(-total_gates/2),int(total_gates/2))
732
+ else:
733
+ out_grid = np.arange(int(-(total_gates-1)/2),int((total_gates-1)/2)+1)
734
+ # 将ougrid转换为经纬度坐标
735
+ out_lon,out_lat = geotrans.cartesian_to_geographic_aeqd(out_grid*range_reso,out_grid*range_reso,dic_scfg['lon'],dic_scfg['lat'])
736
+ outdata = (data_grid - dic_radial_header['offset']) / dic_radial_header['scale']
737
+ minvalue = (dic_radial_header['min_value'] - dic_radial_header['offset']) / dic_radial_header['scale']
738
+ maxvalue = (dic_radial_header['max_value'] - dic_radial_header['offset']) / dic_radial_header['scale']
739
+ # outdata[outdata > 90] = -32
740
+ # outdata[outdata < -10] = -32
741
+ outdata[outdata >= maxvalue] = -9999
742
+ outdata[outdata <= minvalue] = -9999
743
+ varname = 'ref'
744
+ units = 'dBZ'
745
+ if dic_radial_header['data_type'] == 7:
746
+ varname = 'zdr'
747
+ units = 'dB'
748
+ elif dic_radial_header['data_type'] == 9:
749
+ varname = 'cc'
750
+ units = '%'
751
+ elif dic_radial_header['data_type'] == 11:
752
+ varname = 'kdp'
753
+ units = 'deg/km'
754
+ pass
755
+ data = xr.DataArray(np.array(outdata.transpose()),coords=[out_lat,out_lon],dims=['lat','lon'],name=varname)
756
+ data.attrs['units'] = units
757
+ # data.attrs['standard_name'] = 'equivalent_reflectivity_factor'
758
+ # data.attrs['long_name'] = 'equivalent_reflectivity_factor'
759
+ data.attrs['varname'] = varname
760
+ data.attrs['radar_lat'] = dic_scfg['lat']
761
+ data.attrs['radar_lon'] = dic_scfg['lon']
762
+ data.attrs['ana_height'] = dic_scfg['ana_height']
763
+ data.attrs['grid_num'] = total_gates
764
+ data.attrs['grid_reso'] = range_reso
765
+ data.attrs['elevation'] = ele
766
+ data.attrs['obs_range'] = int(range_reso * (total_gates-1)/2)
767
+ data.attrs['distance_unit'] = 'meter'
768
+ data.attrs['missing_value'] = -9999
769
+ data.attrs['datatype'] = 'float32'
770
+ # data.attrs['decode_method'] = 'dbz = (data - %d) / %d'%(dic_radial_header['offset'],dic_radial_header['scale'])
771
+ data.attrs['task_name'] = dic_tcfg['task_name'].decode('utf-8').strip('\x00')
772
+ data.attrs['radar_type'] = dic_scfg['radar_type']
773
+ data.attrs['scan_time'] = datetime.fromtimestamp(dic_tcfg['scan_stime']).strftime('%Y-%m-%d %H:%M:%S')
774
+ try:
775
+ data.attrs['site_name'] = dic_scfg['site_name'].decode('utf-8').strip('\x00')
776
+ except:
777
+ data.attrs['site_name'] = 'Unknown'
778
+ data.attrs['site_id'] = dic_scfg['site_code'].decode('utf-8').strip('\x00')
779
+ # data.attrs['offset'] = dic_radial_header['offset']
780
+ # data.attrs['scale'] = dic_radial_header['scale']
781
+
782
+ return data
783
+
784
+ # 解析cr产品文件,栅格数据
785
+ def read_cr(self,filepath,filename):
786
+ '''
787
+ 解析cr产品文件
788
+ '''
789
+ pass
790
+
791
+ fin = open(filepath + os.sep + filename,'rb')
792
+ buf = fin.read()
793
+ fin.close()
794
+ buf_length = len(buf)
795
+ pos = 0
796
+
797
+ # 获取通用信息头
798
+ dic_gh = rs._unpack_from_buf(buf, pos, rs.GENERIC_HEADER)
799
+ # pprint(dic_gh)
800
+ if dic_gh['magic_number'] != 0x4D545352:
801
+ print('源数据格式错误!')
802
+ return None
803
+ pos = pos + rs._structure_size(rs.GENERIC_HEADER)
804
+
805
+ # 获取站点信息
806
+ dic_scfg = rs._unpack_from_buf(buf, pos, rs.SITE_CONFIG)
807
+ pos = pos + rs._structure_size(rs.SITE_CONFIG)
808
+
809
+ # 获取任务信息
810
+ dic_tcfg = rs._unpack_from_buf(buf, pos, rs.TASK_CONFIG)
811
+ pos = pos + rs._structure_size(rs.TASK_CONFIG)
812
+
813
+ # 获取扫描信息
814
+ cutinfo = []
815
+ for im in np.arange(dic_tcfg['cut_number']):
816
+ dic_cutcfg = rs._unpack_from_buf(buf, pos, rs.SCAN_CONFIG)
817
+ cutinfo.append(dic_cutcfg)
818
+ pos = pos + rs._structure_size(rs.SCAN_CONFIG)
819
+
820
+ # 获取产品头信息
821
+ dic_prod_header = rs._unpack_from_buf(buf, pos, rs.PRODUCT_HEADER_BLOCK)
822
+ pos = pos + rs._structure_size(rs.PRODUCT_HEADER_BLOCK)
823
+
824
+ # 获取产品参数信息
825
+ prod_type = rs.PRODUCT_ID_NAME_MAP[dic_prod_header['product_type']]
826
+ dic_prod_param = rs._unpack_from_buf(buf, pos, rs.PRODUCT_DEPENDENT_PARAMETER[prod_type])
827
+ print('产品类型: %d-%s'%(dic_prod_header['product_type'],prod_type))
828
+ pos = pos + 64 # 产品参数长度固定为64个
829
+
830
+ # 读取产品径向头信息
831
+ dic_grid_header = rs._unpack_from_buf(buf, pos, rs.GRID_HEADER)
832
+ pos = pos + rs._structure_size(rs.GRID_HEADER)
833
+ print('数据类型: %d-%s'%(dic_grid_header['data_type'],rs.PRODUCT_DATA_TYPE[dic_grid_header['data_type']]))
834
+ # Value=(Code-Offset)/Scale
835
+ # 读取栅格数据GRID_DATA
836
+
837
+ if dic_grid_header['bin_length'] == 1:
838
+ data1 = np.frombuffer(buf[pos:pos + dic_grid_header['row_side_len']*dic_grid_header['col_side_len']*dic_grid_header['bin_length']], '>u1')
839
+ data1 = data1.astype('uint8')
840
+ data2 = np.reshape(data1,(dic_grid_header['row_side_len'],dic_grid_header['col_side_len']))
841
+ # data2[data2<dic_grid_header['min_value']]=255
842
+ # 根据offset和scale进行解码
843
+ data2 = (data2 - dic_grid_header['offset']) / dic_grid_header['scale']
844
+ data2[data2 > 90] = -32
845
+ data2[data2 < -10] = -32
846
+ nlat = dic_grid_header['row_side_len']
847
+ nlon = dic_grid_header['col_side_len']
848
+ # print(nlat,nlon)
849
+ lat_reso = dic_grid_header['row_resolution']
850
+ lon_reso = dic_grid_header['col_resolution']
851
+
852
+ # 将data由list转成二维numpy数组
853
+
854
+
855
+ # 将直角坐标转换成经纬度坐标
856
+
857
+ if nlat % 2 == 0:
858
+ out_grid_lat = np.arange(int(-nlat/2),int(nlat/2))
859
+ else:
860
+ out_grid_lat = np.arange(int(-(nlat-1)/2),int((nlat-1)/2)+1)
861
+
862
+ if nlon % 2 == 0:
863
+ out_grid_lon = np.arange(int(-nlon/2),int(nlon/2))
864
+ else:
865
+ out_grid_lon = np.arange(int(-(nlon-1)/2),int((nlon-1)/2)+1)
866
+
867
+ # 将ougrid转换为经纬度坐标
868
+ out_lon,out_lat = geotrans.cartesian_to_geographic_aeqd(out_grid_lat*lat_reso,out_grid_lon*lon_reso,dic_scfg['lon'],dic_scfg['lat'])
869
+
870
+ data = xr.DataArray(np.flipud(data2),coords=[out_lat,out_lon],dims=['lat','lon'],name='cref')
871
+ data.attrs['units'] = 'dBZ'
872
+ data.attrs['standard_name'] = 'composite_reflectivity_factor'
873
+ data.attrs['long_name'] = 'composite_reflectivity_factor'
874
+ data.attrs['radar_lat'] = dic_scfg['lat']
875
+ data.attrs['radar_lon'] = dic_scfg['lon']
876
+ data.attrs['ana_height'] = dic_scfg['ana_height']
877
+ data.attrs['lat_grid_num'] = nlat
878
+ data.attrs['lon_grid_num'] = nlon
879
+ data.attrs['lat_grid_reso'] = dic_grid_header['row_resolution']
880
+ data.attrs['lon_grid_reso'] = dic_grid_header['col_resolution']
881
+ data.attrs['distance_unit'] = 'meter'
882
+ data.attrs['missing_value'] = -32
883
+ data.attrs['datatype'] = 'float32'
884
+ # data.attrs['decode_method'] = 'dbz = (data - %d) / %d'%(dic_grid_header['offset'],dic_grid_header['scale'])
885
+ data.attrs['task_name'] = dic_tcfg['task_name'].decode('utf-8').strip('\x00')
886
+ data.attrs['radar_type'] = dic_scfg['radar_type']
887
+ data.attrs['scan_time'] = datetime.fromtimestamp(dic_tcfg['scan_stime']).strftime('%Y-%m-%d %H:%M:%S')
888
+ try:
889
+ data.attrs['site_name'] = dic_scfg['site_name'].decode('utf-8').strip('\x00')
890
+ except:
891
+ data.attrs['site_name'] = 'Unknown'
892
+ data.attrs['site_id'] = dic_scfg['site_code'].decode('utf-8').strip('\x00')
893
+
894
+ return data
895
+
896
+ # 解析vil产品文件,栅格数据
897
+ def read_vil(self,filepath,filename):
898
+ '''
899
+ 解析vil产品文件
900
+ '''
901
+ pass
902
+
903
+ fin = open(filepath + os.sep + filename,'rb')
904
+ buf = fin.read()
905
+ fin.close()
906
+ buf_length = len(buf)
907
+ pos = 0
908
+
909
+ # 获取通用信息头
910
+ dic_gh = rs._unpack_from_buf(buf, pos, rs.GENERIC_HEADER)
911
+ # pprint(dic_gh)
912
+ if dic_gh['magic_number'] != 0x4D545352:
913
+ print('源数据格式错误!')
914
+ pos = pos + rs._structure_size(rs.GENERIC_HEADER)
915
+
916
+ # 获取站点信息
917
+ dic_scfg = rs._unpack_from_buf(buf, pos, rs.SITE_CONFIG)
918
+ pos = pos + rs._structure_size(rs.SITE_CONFIG)
919
+
920
+ # 获取任务信息
921
+ dic_tcfg = rs._unpack_from_buf(buf, pos, rs.TASK_CONFIG)
922
+ pos = pos + rs._structure_size(rs.TASK_CONFIG)
923
+
924
+ # 获取扫描信息
925
+ cutinfo = []
926
+ for im in np.arange(dic_tcfg['cut_number']):
927
+ dic_cutcfg = rs._unpack_from_buf(buf, pos, rs.SCAN_CONFIG)
928
+ cutinfo.append(dic_cutcfg)
929
+ pos = pos + rs._structure_size(rs.SCAN_CONFIG)
930
+
931
+ # 获取产品头信息
932
+ dic_prod_header = rs._unpack_from_buf(buf, pos, rs.PRODUCT_HEADER_BLOCK)
933
+ pos = pos + rs._structure_size(rs.PRODUCT_HEADER_BLOCK)
934
+
935
+ # 获取产品参数信息
936
+ prod_type = rs.PRODUCT_ID_NAME_MAP[dic_prod_header['product_type']]
937
+ dic_prod_param = rs._unpack_from_buf(buf, pos, rs.PRODUCT_DEPENDENT_PARAMETER[prod_type])
938
+ print('产品类型: %d-%s'%(dic_prod_header['product_type'],prod_type))
939
+ pos = pos + 64 # 产品参数长度固定为64个
940
+
941
+ # 读取产品径向头信息
942
+ dic_grid_header = rs._unpack_from_buf(buf, pos, rs.GRID_HEADER)
943
+ pos = pos + rs._structure_size(rs.GRID_HEADER)
944
+ print('数据类型: %d-%s'%(dic_grid_header['data_type'],rs.PRODUCT_DATA_TYPE[dic_grid_header['data_type']]))
945
+ # Value=(Code-Offset)/Scale
946
+ # 读取栅格数据GRID_DATA
947
+
948
+ if dic_grid_header['bin_length'] == 1:
949
+ data1 = np.frombuffer(buf[pos:pos + dic_grid_header['row_side_len']*dic_grid_header['col_side_len']*dic_grid_header['bin_length']], '>u1')
950
+ data1 = data1.astype('uint8')
951
+ data2 = np.reshape(data1,(dic_grid_header['row_side_len'],dic_grid_header['col_side_len']))
952
+ # data2[data2<dic_grid_header['min_value']]=255
953
+ # 根据offset和scale进行解码
954
+ data2 = (data2 - dic_grid_header['offset']) / dic_grid_header['scale']
955
+ data2[data2 > 120] = 0
956
+ data2[data2 < 0] = 0
957
+ nlat = dic_grid_header['row_side_len']
958
+ nlon = dic_grid_header['col_side_len']
959
+ # print(nlat,nlon)
960
+ lat_reso = dic_grid_header['row_resolution']
961
+ lon_reso = dic_grid_header['col_resolution']
962
+
963
+ # 将data由list转成二维numpy数组
964
+
965
+
966
+ # 将直角坐标转换成经纬度坐标
967
+
968
+ if nlat % 2 == 0:
969
+ out_grid_lat = np.arange(int(-nlat/2),int(nlat/2))
970
+ else:
971
+ out_grid_lat = np.arange(int(-(nlat-1)/2),int((nlat-1)/2)+1)
972
+
973
+ if nlon % 2 == 0:
974
+ out_grid_lon = np.arange(int(-nlon/2),int(nlon/2))
975
+ else:
976
+ out_grid_lon = np.arange(int(-(nlon-1)/2),int((nlon-1)/2)+1)
977
+
978
+ # 将ougrid转换为经纬度坐标
979
+ out_lon,out_lat = geotrans.cartesian_to_geographic_aeqd(out_grid_lat*lat_reso,out_grid_lon*lon_reso,dic_scfg['lon'],dic_scfg['lat'])
980
+
981
+ data = xr.DataArray(np.flipud(data2),coords=[out_lat,out_lon],dims=['lat','lon'],name='vil')
982
+ data.attrs['units'] = 'kg/m^2'
983
+ data.attrs['standard_name'] = 'vertically_integrated_liquid_water'
984
+ data.attrs['long_name'] = 'vertically_integrated_liquid_water'
985
+ data.attrs['radar_lat'] = dic_scfg['lat']
986
+ data.attrs['radar_lon'] = dic_scfg['lon']
987
+ data.attrs['ana_height'] = dic_scfg['ana_height']
988
+ data.attrs['lat_grid_num'] = nlat
989
+ data.attrs['lon_grid_num'] = nlon
990
+ data.attrs['lat_grid_reso'] = dic_grid_header['row_resolution']
991
+ data.attrs['lon_grid_reso'] = dic_grid_header['col_resolution']
992
+ data.attrs['distance_unit'] = 'meter'
993
+ data.attrs['missing_value'] = 0
994
+ data.attrs['datatype'] = 'float32'
995
+ # data.attrs['decode_method'] = 'dbz = (data - %d) / %d'%(dic_grid_header['offset'],dic_grid_header['scale'])
996
+ data.attrs['task_name'] = dic_tcfg['task_name'].decode('utf-8').strip('\x00')
997
+ data.attrs['radar_type'] = dic_scfg['radar_type']
998
+ data.attrs['scan_time'] = datetime.fromtimestamp(dic_tcfg['scan_stime']).strftime('%Y-%m-%d %H:%M:%S')
999
+ try:
1000
+ data.attrs['site_name'] = dic_scfg['site_name'].decode('utf-8').strip('\x00')
1001
+ except:
1002
+ data.attrs['site_name'] = 'Unknown'
1003
+ data.attrs['site_id'] = dic_scfg['site_code'].decode('utf-8').strip('\x00')
1004
+
1005
+ return data
1006
+
1007
+ # 解析tops产品文件,栅格数据,回波顶高
1008
+ def read_tops(self,filepath,filename):
1009
+ '''
1010
+ 解析tops产品文件
1011
+ '''
1012
+ pass
1013
+
1014
+ fin = open(filepath + os.sep + filename,'rb')
1015
+ buf = fin.read()
1016
+ fin.close()
1017
+ buf_length = len(buf)
1018
+ pos = 0
1019
+
1020
+ # 获取通用信息头
1021
+ dic_gh = rs._unpack_from_buf(buf, pos, rs.GENERIC_HEADER)
1022
+ # pprint(dic_gh)
1023
+ if dic_gh['magic_number'] != 0x4D545352:
1024
+ print('源数据格式错误!')
1025
+ pos = pos + rs._structure_size(rs.GENERIC_HEADER)
1026
+
1027
+ # 获取站点信息
1028
+ dic_scfg = rs._unpack_from_buf(buf, pos, rs.SITE_CONFIG)
1029
+ pos = pos + rs._structure_size(rs.SITE_CONFIG)
1030
+
1031
+ # 获取任务信息
1032
+ dic_tcfg = rs._unpack_from_buf(buf, pos, rs.TASK_CONFIG)
1033
+ pos = pos + rs._structure_size(rs.TASK_CONFIG)
1034
+
1035
+ # 获取扫描信息
1036
+ cutinfo = []
1037
+ for im in np.arange(dic_tcfg['cut_number']):
1038
+ dic_cutcfg = rs._unpack_from_buf(buf, pos, rs.SCAN_CONFIG)
1039
+ cutinfo.append(dic_cutcfg)
1040
+ pos = pos + rs._structure_size(rs.SCAN_CONFIG)
1041
+
1042
+ # 获取产品头信息
1043
+ dic_prod_header = rs._unpack_from_buf(buf, pos, rs.PRODUCT_HEADER_BLOCK)
1044
+ pos = pos + rs._structure_size(rs.PRODUCT_HEADER_BLOCK)
1045
+
1046
+ # 获取产品参数信息
1047
+ prod_type = rs.PRODUCT_ID_NAME_MAP[dic_prod_header['product_type']]
1048
+ dic_prod_param = rs._unpack_from_buf(buf, pos, rs.PRODUCT_DEPENDENT_PARAMETER[prod_type])
1049
+ print('产品类型: %d-%s'%(dic_prod_header['product_type'],prod_type))
1050
+ pos = pos + 64 # 产品参数长度固定为64个
1051
+
1052
+ # 读取产品径向头信息
1053
+ dic_grid_header = rs._unpack_from_buf(buf, pos, rs.GRID_HEADER)
1054
+ pos = pos + rs._structure_size(rs.GRID_HEADER)
1055
+ print('数据类型: %d-%s'%(dic_grid_header['data_type'],rs.PRODUCT_DATA_TYPE[dic_grid_header['data_type']]))
1056
+ # Value=(Code-Offset)/Scale
1057
+ # 读取栅格数据GRID_DATA
1058
+
1059
+ if dic_grid_header['bin_length'] == 1:
1060
+ data1 = np.frombuffer(buf[pos:pos + dic_grid_header['row_side_len']*dic_grid_header['col_side_len']*dic_grid_header['bin_length']], '>u1')
1061
+ data1 = data1.astype('uint8')
1062
+ data2 = np.reshape(data1,(dic_grid_header['row_side_len'],dic_grid_header['col_side_len']))
1063
+ # data2[data2<dic_grid_header['min_value']]=255
1064
+ # 根据offset和scale进行解码
1065
+
1066
+
1067
+
1068
+ data2[data2 > dic_grid_header['max_value']] = 0
1069
+ data2 = (data2 - dic_grid_header['offset']) / dic_grid_header['scale']
1070
+ maxv = (dic_grid_header['max_value'] - dic_grid_header['offset']) / dic_grid_header['scale']
1071
+ data2[data2 > maxv] = 0
1072
+
1073
+ nlat = dic_grid_header['row_side_len']
1074
+ nlon = dic_grid_header['col_side_len']
1075
+ # print(nlat,nlon)
1076
+ lat_reso = dic_grid_header['row_resolution']
1077
+ lon_reso = dic_grid_header['col_resolution']
1078
+
1079
+ # 将data由list转成二维numpy数组
1080
+
1081
+
1082
+ # 将直角坐标转换成经纬度坐标
1083
+
1084
+ if nlat % 2 == 0:
1085
+ out_grid_lat = np.arange(int(-nlat/2),int(nlat/2))
1086
+ else:
1087
+ out_grid_lat = np.arange(int(-(nlat-1)/2),int((nlat-1)/2)+1)
1088
+
1089
+ if nlon % 2 == 0:
1090
+ out_grid_lon = np.arange(int(-nlon/2),int(nlon/2))
1091
+ else:
1092
+ out_grid_lon = np.arange(int(-(nlon-1)/2),int((nlon-1)/2)+1)
1093
+
1094
+ # 将ougrid转换为经纬度坐标
1095
+ out_lon,out_lat = geotrans.cartesian_to_geographic_aeqd(out_grid_lat*lat_reso,out_grid_lon*lon_reso,dic_scfg['lon'],dic_scfg['lat'])
1096
+
1097
+ data = xr.DataArray(np.flipud(data2),coords=[out_lat,out_lon],dims=['lat','lon'],name='et')
1098
+ data.attrs['units'] = 'km'
1099
+ data.attrs['standard_name'] = 'echo_top'
1100
+ data.attrs['long_name'] = 'echo_top'
1101
+ data.attrs['radar_lat'] = dic_scfg['lat']
1102
+ data.attrs['radar_lon'] = dic_scfg['lon']
1103
+ data.attrs['ana_height'] = dic_scfg['ana_height']
1104
+ data.attrs['lat_grid_num'] = nlat
1105
+ data.attrs['lon_grid_num'] = nlon
1106
+ data.attrs['lat_grid_reso'] = dic_grid_header['row_resolution']
1107
+ data.attrs['lon_grid_reso'] = dic_grid_header['col_resolution']
1108
+ data.attrs['distance_unit'] = 'kilometer'
1109
+ data.attrs['missing_value'] = 0
1110
+ data.attrs['datatype'] = 'float32'
1111
+ # data.attrs['decode_method'] = 'dbz = (data - %d) / %d'%(dic_grid_header['offset'],dic_grid_header['scale'])
1112
+ data.attrs['task_name'] = dic_tcfg['task_name'].decode('utf-8').strip('\x00')
1113
+ data.attrs['radar_type'] = dic_scfg['radar_type']
1114
+ data.attrs['scan_time'] = datetime.fromtimestamp(dic_tcfg['scan_stime']).strftime('%Y-%m-%d %H:%M:%S')
1115
+ try:
1116
+ data.attrs['site_name'] = dic_scfg['site_name'].decode('utf-8').strip('\x00')
1117
+ except:
1118
+ data.attrs['site_name'] = 'Unknown'
1119
+ data.attrs['site_id'] = dic_scfg['site_code'].decode('utf-8').strip('\x00')
1120
+
1121
+ return data
1122
+
1123
+
1124
+ # 解析stp产品文件,栅格数据
1125
+ def read_stp(self,filepath,filename):
1126
+ '''
1127
+ 解析stp产品文件
1128
+ '''
1129
+ pass
1130
+
1131
+ fin = open(filepath + os.sep + filename,'rb')
1132
+ buf = fin.read()
1133
+ fin.close()
1134
+ buf_length = len(buf)
1135
+ pos = 0
1136
+
1137
+ # 获取通用信息头
1138
+ dic_gh = rs._unpack_from_buf(buf, pos, rs.GENERIC_HEADER)
1139
+ # pprint(dic_gh)
1140
+ if dic_gh['magic_number'] != 0x4D545352:
1141
+ print('源数据格式错误!')
1142
+ pos = pos + rs._structure_size(rs.GENERIC_HEADER)
1143
+
1144
+ # 获取站点信息
1145
+ dic_scfg = rs._unpack_from_buf(buf, pos, rs.SITE_CONFIG)
1146
+ pos = pos + rs._structure_size(rs.SITE_CONFIG)
1147
+
1148
+ # 获取任务信息
1149
+ dic_tcfg = rs._unpack_from_buf(buf, pos, rs.TASK_CONFIG)
1150
+ pos = pos + rs._structure_size(rs.TASK_CONFIG)
1151
+
1152
+ # 获取扫描信息
1153
+ cutinfo = []
1154
+ for im in np.arange(dic_tcfg['cut_number']):
1155
+ dic_cutcfg = rs._unpack_from_buf(buf, pos, rs.SCAN_CONFIG)
1156
+ cutinfo.append(dic_cutcfg)
1157
+ pos = pos + rs._structure_size(rs.SCAN_CONFIG)
1158
+
1159
+ # 获取产品头信息
1160
+ dic_prod_header = rs._unpack_from_buf(buf, pos, rs.PRODUCT_HEADER_BLOCK)
1161
+ pos = pos + rs._structure_size(rs.PRODUCT_HEADER_BLOCK)
1162
+
1163
+ # 获取产品参数信息
1164
+ prod_type = rs.PRODUCT_ID_NAME_MAP[dic_prod_header['product_type']]
1165
+ dic_prod_param = rs._unpack_from_buf(buf, pos, rs.PRODUCT_DEPENDENT_PARAMETER[prod_type])
1166
+ print('产品类型: %d-%s'%(dic_prod_header['product_type'],prod_type))
1167
+ pos = pos + 64 # 产品参数长度固定为64个
1168
+
1169
+ # 读取产品径向头信息
1170
+ dic_radial_header = rs._unpack_from_buf(buf, pos, rs.RADIAL_HEADER)
1171
+ pos = pos + rs._structure_size(rs.RADIAL_HEADER)
1172
+ print('数据类型: %d-%s'%(dic_radial_header['data_type'],rs.PRODUCT_DATA_TYPE[dic_radial_header['data_type']]))
1173
+ # Value=(Code-Offset)/Scale
1174
+ # 读取径向数据RADIAL_DATA
1175
+ data=[]
1176
+ data_azi=[]
1177
+ binnum = None
1178
+ for nn in np.arange(dic_radial_header['radial_number']):
1179
+ radial_data_info = rs._unpack_from_buf(buf, pos, rs.RADIAL_DATA)
1180
+ pos = pos + rs._structure_size(rs.RADIAL_DATA)
1181
+ binnum = radial_data_info['num_bins']
1182
+ # rdata = buf[pos:pos+dic_radial_header['bin_length']*radial_data_info['num_bins']]
1183
+ rdata = np.frombuffer(buf[pos:pos+dic_radial_header['bin_length']*radial_data_info['num_bins']], '<u2')
1184
+ data.extend(rdata)
1185
+ pos = pos + dic_radial_header['bin_length']*radial_data_info['num_bins']
1186
+ # print(radial_data_info['start_azi'])
1187
+ data_azi.append(radial_data_info['start_azi'])
1188
+ pass
1189
+ data = np.array(data)
1190
+ # data[data > dic_radial_header['max_value']] = 0
1191
+ # data[data < dic_radial_header['min_value']] = 0
1192
+
1193
+ data = data.astype('uint16')
1194
+ nx = dic_radial_header['radial_number']
1195
+ ny = binnum
1196
+ print(nx,ny)
1197
+ # 将data转成numpy数组
1198
+ data = data.reshape((nx,ny))
1199
+
1200
+ # 将方位角坐标转换成经纬度坐标
1201
+ sweep_azimuths =np.array(data_azi)
1202
+
1203
+ range_reso = dic_radial_header['resolution']
1204
+ # 输出的距离库只保留一半
1205
+ ngates = int(ny//2*2) #//2*2
1206
+ grid_x = np.arange(-1*ngates,ngates+1,1)
1207
+ grid_y = np.arange(-1*ngates,ngates+1,1)
1208
+ total_gates = len(grid_x)
1209
+
1210
+ aa = np.meshgrid(grid_x,grid_y)
1211
+ azi_grid = np.arctan2(aa[0],aa[1])*180/np.pi
1212
+ azi_grid[azi_grid<0]+=360
1213
+
1214
+ azi_reso = 360/len(sweep_azimuths)
1215
+
1216
+ # 求方位角索引
1217
+ new_azi = azi_grid.flatten()
1218
+ t = new_azi-sweep_azimuths[0]
1219
+ t[t<0]+=360
1220
+ ray_number = np.round(t/azi_reso,0).astype(int)
1221
+ ray_number[ray_number==len(sweep_azimuths)]=0
1222
+ ray_number = np.reshape(ray_number,(total_gates,total_gates))
1223
+
1224
+ # 求距离索引
1225
+ dis_grid = np.sqrt(aa[0]**2 + aa[1]**2)
1226
+ dis_grid = np.round(dis_grid.flatten(),0).astype(int)
1227
+ dis_grid = np.reshape(dis_grid,(total_gates,total_gates))
1228
+
1229
+ # 对数据进行截断,在径向方向上
1230
+ data = data[:,0:ngates]
1231
+
1232
+ data_grid = np.zeros((total_gates,total_gates),dtype='uint16') + 0
1233
+ new_data = data_grid.flatten()
1234
+ new_spdata = data.flatten()
1235
+ # decode data
1236
+ new_spdata = (new_spdata - dic_radial_header['offset']) / dic_radial_header['scale']
1237
+ pos_out = [i+j*total_gates for i in range(total_gates) for j in range(total_gates) if dis_grid[i,j] < ngates]
1238
+ d_out = [rn*ngates+dg for rn,dg in zip(ray_number.flatten(),dis_grid.flatten()) if dg < ngates]
1239
+
1240
+ new_data[pos_out]=new_spdata[d_out]
1241
+
1242
+ data_grid = np.reshape(new_data,(total_gates,total_gates))
1243
+
1244
+
1245
+ if total_gates % 2 == 0:
1246
+ out_grid = np.arange(int(-total_gates/2),int(total_gates/2))
1247
+ else:
1248
+ out_grid = np.arange(int(-(total_gates-1)/2),int((total_gates-1)/2)+1)
1249
+ # 将ougrid转换为经纬度坐标
1250
+ out_lon,out_lat = geotrans.cartesian_to_geographic_aeqd(out_grid*range_reso,out_grid*range_reso,dic_scfg['lon'],dic_scfg['lat'])
1251
+
1252
+ data = xr.DataArray(np.array(data_grid.transpose()),coords=[out_lat,out_lon],dims=['lat','lon'],name='stp')
1253
+ data.attrs['units'] = 'mm'
1254
+ data.attrs['standard_name'] = 'storm total precipitation'
1255
+ data.attrs['long_name'] = 'storm total precipitation'
1256
+ data.attrs['radar_lat'] = dic_scfg['lat']
1257
+ data.attrs['radar_lon'] = dic_scfg['lon']
1258
+ data.attrs['ana_height'] = dic_scfg['ana_height']
1259
+ data.attrs['grid_num'] = total_gates
1260
+ data.attrs['grid_reso'] = range_reso
1261
+ data.attrs['obs_range'] = int(range_reso * (total_gates-1)/2)
1262
+ data.attrs['distance_unit'] = 'meter'
1263
+ data.attrs['missing_value'] = 0
1264
+ data.attrs['datatype'] = 'uint16'
1265
+ # data.attrs['decode_method'] = 'qpe = (data - %d) / %d'%(dic_radial_header['offset'],dic_radial_header['scale'])
1266
+ data.attrs['task_name'] = dic_tcfg['task_name'].decode('utf-8').strip('\x00')
1267
+ data.attrs['radar_type'] = dic_scfg['radar_type']
1268
+ data.attrs['scan_time'] = datetime.fromtimestamp(dic_tcfg['scan_stime']).strftime('%Y-%m-%d %H:%M:%S')
1269
+ try:
1270
+ data.attrs['site_name'] = dic_scfg['site_name'].decode('utf-8').strip('\x00')
1271
+ except:
1272
+ data.attrs['site_name'] = 'Unknown'
1273
+ data.attrs['site_id'] = dic_scfg['site_code'].decode('utf-8').strip('\x00')
1274
+ # data.attrs['offset'] = dic_radial_header['offset']
1275
+ # data.attrs['scale'] = dic_radial_header['scale']
1276
+
1277
+ return data
1278
+
1279
+ # 解析ohp产品文件,栅格数据
1280
+ def read_ohp(self,filepath,filename):
1281
+ '''
1282
+ 解析ohp产品文件
1283
+ '''
1284
+ pass
1285
+
1286
+ fin = open(filepath + os.sep + filename,'rb')
1287
+ buf = fin.read()
1288
+ fin.close()
1289
+ buf_length = len(buf)
1290
+ pos = 0
1291
+
1292
+ # 获取通用信息头
1293
+ dic_gh = rs._unpack_from_buf(buf, pos, rs.GENERIC_HEADER)
1294
+ # pprint(dic_gh)
1295
+ if dic_gh['magic_number'] != 0x4D545352:
1296
+ print('源数据格式错误!')
1297
+ return None
1298
+ pos = pos + rs._structure_size(rs.GENERIC_HEADER)
1299
+
1300
+ # 获取站点信息
1301
+ dic_scfg = rs._unpack_from_buf(buf, pos, rs.SITE_CONFIG)
1302
+ pos = pos + rs._structure_size(rs.SITE_CONFIG)
1303
+
1304
+ # 获取任务信息
1305
+ dic_tcfg = rs._unpack_from_buf(buf, pos, rs.TASK_CONFIG)
1306
+ pos = pos + rs._structure_size(rs.TASK_CONFIG)
1307
+
1308
+ # 获取扫描信息
1309
+ cutinfo = []
1310
+ for im in np.arange(dic_tcfg['cut_number']):
1311
+ dic_cutcfg = rs._unpack_from_buf(buf, pos, rs.SCAN_CONFIG)
1312
+ cutinfo.append(dic_cutcfg)
1313
+ pos = pos + rs._structure_size(rs.SCAN_CONFIG)
1314
+
1315
+ # 获取产品头信息
1316
+ dic_prod_header = rs._unpack_from_buf(buf, pos, rs.PRODUCT_HEADER_BLOCK)
1317
+ pos = pos + rs._structure_size(rs.PRODUCT_HEADER_BLOCK)
1318
+
1319
+ # 获取产品参数信息
1320
+ prod_type = rs.PRODUCT_ID_NAME_MAP[dic_prod_header['product_type']]
1321
+ dic_prod_param = rs._unpack_from_buf(buf, pos, rs.PRODUCT_DEPENDENT_PARAMETER[prod_type])
1322
+ print('产品类型: %d-%s'%(dic_prod_header['product_type'],prod_type))
1323
+ pos = pos + 64 # 产品参数长度固定为64个
1324
+
1325
+ # 读取产品径向头信息
1326
+ dic_radial_header = rs._unpack_from_buf(buf, pos, rs.RADIAL_HEADER)
1327
+ pos = pos + rs._structure_size(rs.RADIAL_HEADER)
1328
+ print('数据类型: %d-%s'%(dic_radial_header['data_type'],rs.PRODUCT_DATA_TYPE[dic_radial_header['data_type']]))
1329
+ # Value=(Code-Offset)/Scale
1330
+ # 读取径向数据RADIAL_DATA
1331
+ data=[]
1332
+ data_azi=[]
1333
+ binnum = None
1334
+ for nn in np.arange(dic_radial_header['radial_number']):
1335
+ radial_data_info = rs._unpack_from_buf(buf, pos, rs.RADIAL_DATA)
1336
+ pos = pos + rs._structure_size(rs.RADIAL_DATA)
1337
+ binnum = radial_data_info['num_bins']
1338
+ # rdata = buf[pos:pos+dic_radial_header['bin_length']*radial_data_info['num_bins']]
1339
+ rdata = np.frombuffer(buf[pos:pos+dic_radial_header['bin_length']*radial_data_info['num_bins']], '<u2')
1340
+ data.extend(rdata)
1341
+ pos = pos + dic_radial_header['bin_length']*radial_data_info['num_bins']
1342
+ # print(radial_data_info['start_azi'])
1343
+ data_azi.append(radial_data_info['start_azi'])
1344
+ pass
1345
+ data = np.array(data)
1346
+ data[data > dic_radial_header['max_value']] = 0
1347
+ data[data < dic_radial_header['min_value']] = 0
1348
+
1349
+ data = data.astype('uint16')
1350
+ nx = dic_radial_header['radial_number']
1351
+ ny = binnum
1352
+ print(nx,ny)
1353
+ # 将data转成numpy数组
1354
+ data = data.reshape((nx,ny))
1355
+
1356
+ # decode data
1357
+ # ppiarray = (ppiarray - dic_radial_header['offset']) / dic_radial_header['scale']
1358
+
1359
+
1360
+ # 将方位角坐标转换成经纬度坐标
1361
+ sweep_azimuths =np.array(data_azi)
1362
+
1363
+ range_reso = dic_radial_header['resolution']
1364
+ # 输出的距离库只保留一半
1365
+ ngates = int(ny//2*2) #//2*2
1366
+ grid_x = np.arange(-1*ngates,ngates+1,1)
1367
+ grid_y = np.arange(-1*ngates,ngates+1,1)
1368
+ total_gates = len(grid_x)
1369
+
1370
+ aa = np.meshgrid(grid_x,grid_y)
1371
+ azi_grid = np.arctan2(aa[0],aa[1])*180/np.pi
1372
+ azi_grid[azi_grid<0]+=360
1373
+
1374
+ azi_reso = 360/len(sweep_azimuths)
1375
+
1376
+ # 求方位角索引
1377
+ new_azi = azi_grid.flatten()
1378
+ t = new_azi-sweep_azimuths[0]
1379
+ t[t<0]+=360
1380
+ ray_number = np.round(t/azi_reso,0).astype(int)
1381
+ ray_number[ray_number==len(sweep_azimuths)]=0
1382
+ ray_number = np.reshape(ray_number,(total_gates,total_gates))
1383
+
1384
+ # 求距离索引
1385
+ dis_grid = np.sqrt(aa[0]**2 + aa[1]**2)
1386
+ dis_grid = np.round(dis_grid.flatten(),0).astype(int)
1387
+ dis_grid = np.reshape(dis_grid,(total_gates,total_gates))
1388
+
1389
+ # 对数据进行截断,在径向方向上
1390
+ data = data[:,0:ngates]
1391
+
1392
+ data_grid = np.zeros((total_gates,total_gates),dtype='uint16') + 0
1393
+ new_data = data_grid.flatten()
1394
+ new_spdata = data.flatten()
1395
+
1396
+ new_spdata = (new_spdata - dic_radial_header['offset']) / dic_radial_header['scale']
1397
+
1398
+ pos_out = [i+j*total_gates for i in range(total_gates) for j in range(total_gates) if dis_grid[i,j] < ngates]
1399
+ d_out = [rn*ngates+dg for rn,dg in zip(ray_number.flatten(),dis_grid.flatten()) if dg < ngates]
1400
+
1401
+ new_data[pos_out]=new_spdata[d_out]
1402
+
1403
+ data_grid = np.reshape(new_data,(total_gates,total_gates))
1404
+
1405
+
1406
+ if total_gates % 2 == 0:
1407
+ out_grid = np.arange(int(-total_gates/2),int(total_gates/2))
1408
+ else:
1409
+ out_grid = np.arange(int(-(total_gates-1)/2),int((total_gates-1)/2)+1)
1410
+ # 将ougrid转换为经纬度坐标
1411
+ out_lon,out_lat = geotrans.cartesian_to_geographic_aeqd(out_grid*range_reso,out_grid*range_reso,dic_scfg['lon'],dic_scfg['lat'])
1412
+
1413
+ data = xr.DataArray(np.array(data_grid.transpose()),coords=[out_lat,out_lon],dims=['lat','lon'],name='ohp')
1414
+ data.attrs['units'] = 'mm'
1415
+ data.attrs['standard_name'] = 'one hour precipitation'
1416
+ data.attrs['long_name'] = 'one hour precipitation'
1417
+ data.attrs['radar_lat'] = dic_scfg['lat']
1418
+ data.attrs['radar_lon'] = dic_scfg['lon']
1419
+ data.attrs['ana_height'] = dic_scfg['ana_height']
1420
+ data.attrs['grid_num'] = total_gates
1421
+ data.attrs['grid_reso'] = range_reso
1422
+ data.attrs['obs_range'] = int(range_reso * (total_gates-1)/2)
1423
+ data.attrs['distance_unit'] = 'meter'
1424
+ data.attrs['missing_value'] = 0
1425
+ data.attrs['datatype'] = 'uint16'
1426
+ # data.attrs['decode_method'] = 'qpe = (data - %d) / %d'%(dic_radial_header['offset'],dic_radial_header['scale'])
1427
+ data.attrs['task_name'] = dic_tcfg['task_name'].decode('utf-8').strip('\x00')
1428
+ data.attrs['radar_type'] = dic_scfg['radar_type']
1429
+ data.attrs['scan_time'] = datetime.fromtimestamp(dic_tcfg['scan_stime']).strftime('%Y-%m-%d %H:%M:%S')
1430
+ try:
1431
+ data.attrs['site_name'] = dic_scfg['site_name'].decode('utf-8').strip('\x00')
1432
+ except:
1433
+ data.attrs['site_name'] = 'Unknown'
1434
+ data.attrs['site_id'] = dic_scfg['site_code'].decode('utf-8').strip('\x00')
1435
+ # data.attrs['offset'] = dic_radial_header['offset']
1436
+ # data.attrs['scale'] = dic_radial_header['scale']
1437
+
1438
+ return data
1439
+
1440
+
1441
+ # 解析冰雹指数产品
1442
+ def read_hda(self,filepath,filename):
1443
+ '''
1444
+ 解析hail产品文件
1445
+ '''
1446
+ fin = open(filepath + os.sep + filename,'rb')
1447
+ buf = fin.read()
1448
+ fin.close()
1449
+ buf_length = len(buf)
1450
+ pos = 0
1451
+
1452
+ # 获取通用信息头
1453
+ dic_gh = rs._unpack_from_buf(buf, pos, rs.GENERIC_HEADER)
1454
+ # pprint(dic_gh)
1455
+ if dic_gh['magic_number'] != 0x4D545352:
1456
+ print('源数据格式错误!')
1457
+ pos = pos + rs._structure_size(rs.GENERIC_HEADER)
1458
+
1459
+ # 获取站点信息
1460
+ dic_scfg = rs._unpack_from_buf(buf, pos, rs.SITE_CONFIG)
1461
+ pos = pos + rs._structure_size(rs.SITE_CONFIG)
1462
+
1463
+ # 获取任务信息
1464
+ dic_tcfg = rs._unpack_from_buf(buf, pos, rs.TASK_CONFIG)
1465
+ pos = pos + rs._structure_size(rs.TASK_CONFIG)
1466
+
1467
+ # 获取扫描信息
1468
+ cutinfo = []
1469
+ for im in np.arange(dic_tcfg['cut_number']):
1470
+ dic_cutcfg = rs._unpack_from_buf(buf, pos, rs.SCAN_CONFIG)
1471
+ cutinfo.append(dic_cutcfg)
1472
+ pos = pos + rs._structure_size(rs.SCAN_CONFIG)
1473
+
1474
+ # 获取产品头信息
1475
+ dic_prod_header = rs._unpack_from_buf(buf, pos, rs.PRODUCT_HEADER_BLOCK)
1476
+ pos = pos + rs._structure_size(rs.PRODUCT_HEADER_BLOCK)
1477
+
1478
+ # 获取产品参数信息
1479
+ prod_type = rs.PRODUCT_ID_NAME_MAP[dic_prod_header['product_type']]
1480
+ # pprint(prod_type)
1481
+ dic_prod_param = rs._unpack_from_buf(buf, pos, rs.PRODUCT_DEPENDENT_PARAMETER[prod_type])
1482
+ pos = pos + 64 # 产品参数长度固定为64个
1483
+
1484
+ # 获取产品数据块
1485
+ # 冰雹个数
1486
+ hail_number = rs._unpack_from_buf(buf, pos, rs.HAIL_NUMBER)
1487
+ pos = pos + rs._structure_size(rs.HAIL_NUMBER)
1488
+
1489
+ # TVS表块
1490
+ hail_tab=[]
1491
+ for nn in np.arange(hail_number['hail_number']):
1492
+ hail_tab.append(rs._unpack_from_buf(buf, pos, rs.HAIL_TAB))
1493
+ pos = pos + rs._structure_size(rs.HAIL_TAB)
1494
+
1495
+ # TVS适配数据
1496
+ dic_hail_adapt_param = rs._unpack_from_buf(buf, pos, rs.HAIL_ADAPTATION_DATA)
1497
+ pos = pos + rs._structure_size(rs.HAIL_ADAPTATION_DATA)
1498
+
1499
+ # 将所有的方位角和距离转换成经纬度
1500
+ for nn in np.arange(hail_number['hail_number']):
1501
+ pass
1502
+ x,y,z = geotrans.antenna_to_cartesian(hail_tab[nn]['range']/1000.0,hail_tab[nn]['azi'],0)
1503
+ clon,clat = geotrans.cartesian_to_geographic_aeqd(x,y,dic_scfg['lon'],dic_scfg['lat'])
1504
+ hail_tab[nn]['lon'] = clon[0]
1505
+ hail_tab[nn]['lat'] = clat[0]
1506
+
1507
+ # 将数字序号转换成字母数字组合
1508
+ hail_tab[nn]['hail_id_char'] = self.get_id_char(hail_tab[nn]['hail_id'])
1509
+
1510
+
1511
+ allresult={}
1512
+ allresult['hail'] = hail_tab
1513
+
1514
+ self.hailinfo = allresult
1515
+ return allresult
1516
+
1517
+ # 将风暴追踪产品转换为gr2格式
1518
+ def trans_sti_gr2(self,outpath_gr2=None,outname_gr2=None,m_pret=None,time_type='UTC',bhistory=False,bdebug=False):
1519
+
1520
+ if self.stiinfo is None:
1521
+ print('mesoinfo is None, please run read_sti first!')
1522
+ return False
1523
+
1524
+ # try:
1525
+ # duration 代表该时次产品的可视时间段
1526
+ if not os.path.exists(outpath_gr2):
1527
+ os.makedirs(outpath_gr2)
1528
+
1529
+
1530
+ if outpath_gr2 is None or outname_gr2 is None:
1531
+ print('outpath_gr2 and outname_gr2 params should be set')
1532
+ return False
1533
+
1534
+ if not os.path.exists(outpath_gr2):
1535
+ try:
1536
+ os.makedirs(outpath_gr2)
1537
+ except:
1538
+ print(outpath_gr2 + ' not exist, and fail to create this path!')
1539
+ return False
1540
+
1541
+ of = open(outpath_gr2 + os.sep + outname_gr2,'wt',encoding='GBK')
1542
+
1543
+ # 单个文件就不用信息头了
1544
+ if bdebug and not bhistory:
1545
+ of.write('Title: ROSE2风暴追踪信息\n')
1546
+ of.write('Refreshseconds: 30\n')
1547
+ of.write('Threshold: 500\n')
1548
+ of.write('Font: 1, 20, 1, "Courier New"\n')
1549
+ # of.write('IconFile: 1, 18, 18, 9, 9, 2016_144dpi.png\n')
1550
+ of.write('IconFile: 1, 24, 24, 12, 12, 2016_192dpi.png\n')
1551
+
1552
+ # 这里直接用m_pret 可以保证不会出现重叠的记录
1553
+ if not bdebug:
1554
+ if not m_pret is None and bhistory:
1555
+ pre_time = m_pret + timedelta(minutes=-1*int(6/2))
1556
+ endtime = m_pret + timedelta(minutes=1*int(6/2))
1557
+ elif not bhistory:
1558
+ pre_time = m_pret + timedelta(minutes=-1*int(6/2))
1559
+ endtime = m_pret + timedelta(minutes=30)
1560
+ pass
1561
+
1562
+
1563
+ timerangestr = 'TimeRange: ' + pre_time.strftime('%Y-%m-%dT%H:%M:%S') + ' ' + endtime.strftime('%Y-%m-%dT%H:%M:%S')
1564
+
1565
+ # 实时模式下,改时间一定要放到timerange后面
1566
+ if time_type == "BJT":
1567
+ pre_time = pre_time + timedelta(hours=8)
1568
+ endtime = endtime + timedelta(hours=8)
1569
+
1570
+ if bhistory:
1571
+ timerangestr = 'TimeRange: ' + pre_time.strftime('%Y-%m-%dT%H:%M:%S') + ' ' + endtime.strftime('%Y-%m-%dT%H:%M:%S')
1572
+
1573
+ of.write(timerangestr + '\n')
1574
+
1575
+ for dd in self.stiinfo['track']:
1576
+ if len(dd) > 1:
1577
+ of.write('Line: 2,0\n')
1578
+ for nn in dd:
1579
+ of.write('%.4f,%.4f\n'%(nn[0],nn[1]))
1580
+ of.write('End:\n\n')
1581
+
1582
+ for dd in self.stiinfo['marker_past']:
1583
+ of.write('Icon: %.4f,%.4f,0,1,88\n'%(dd[0],dd[1]))
1584
+ for dd in self.stiinfo['marker_fst']:
1585
+ of.write('Icon: %.4f,%.4f,0,1,91\n'%(dd[0],dd[1]))
1586
+
1587
+ for nn in np.arange(len(self.stiinfo['all_storm_prop'])):
1588
+
1589
+ curstorm = self.stiinfo['all_storm_prop'][nn]
1590
+ clon = curstorm['lon']
1591
+ clat = curstorm['lat']
1592
+
1593
+ lbstr=r'数据来源: ROSE风暴识别产品\n'
1594
+ lbstr += r'风暴编号: %s\n'%curstorm['id_char']
1595
+ lbstr += r'方位角/距离: %.1f度/%.1f公里\n'%(curstorm['azi'],curstorm['range']/1000.0)
1596
+ lbstr += r'风暴移动方向 : %s\n'%(self.get_wind_dir_name((curstorm['mv_dir']+180)%360))
1597
+ lbstr += r'风暴移动速度 : %.1f 公里/小时\n'%(curstorm['mv_spd']*3.6)
1598
+
1599
+ lbstr += r'风暴低高: %.1f 公里\n'%(curstorm['base_height']/1000)
1600
+ lbstr += r'风暴顶高: %.1f 公里\n'%(curstorm['top_height']/1000)
1601
+ lbstr += r'风暴体VIL: %.1f 千克/平方米\n'%(curstorm['vil'])
1602
+ lbstr += r'最大反射率: %d dBZ\n'%(curstorm['max_ref'])
1603
+ lbstr += r'最大反射率高度 : %.1f 公里\n'%(curstorm['max_ref_height']/1000)
1604
+
1605
+ of.write('Icon: %.4f,%.4f,0,1,40,"%s"\n'%(clat,clon,lbstr))
1606
+
1607
+
1608
+ of.write('End:\n\n')
1609
+
1610
+ of.close()
1611
+ print(outpath_gr2 + os.sep + outname_gr2 + ' saved!')
1612
+ return outpath_gr2 + os.sep + outname_gr2
1613
+ # except:
1614
+ # return False
1615
+
1616
+
1617
+ # 将中气旋的信息转换为gr2格式
1618
+ def trans_mda_gr2(self,outpath_gr2=None,outname_gr2=None,m_pret=None,time_type='UTC',bhistory=False,bdebug=False):
1619
+ if self.mesoinfo is None:
1620
+ print('mesoinfo is None, please run read_mda first!')
1621
+ return False
1622
+ try:
1623
+ # duration 代表该时次产品的可视时间段
1624
+ if not os.path.exists(outpath_gr2):
1625
+ os.makedirs(outpath_gr2)
1626
+
1627
+
1628
+ if outpath_gr2 is None or outname_gr2 is None:
1629
+ print('outpath_gr2 and outname_gr2 params should be set')
1630
+ return False
1631
+
1632
+ if not os.path.exists(outpath_gr2):
1633
+ try:
1634
+ os.makedirs(outpath_gr2)
1635
+ except:
1636
+ print(outpath_gr2 + ' not exist, and fail to create this path!')
1637
+ return False
1638
+
1639
+ of = open(outpath_gr2 + os.sep + outname_gr2,'wt',encoding='GBK')
1640
+
1641
+ # 单个文件就不用信息头了
1642
+ if bdebug and not bhistory:
1643
+ of.write('Title: ROSE2中气旋识别产品\n')
1644
+ of.write('Refreshseconds: 30\n')
1645
+ of.write('Threshold: 500\n')
1646
+ of.write('Font: 1, 20, 1, "Courier New"\n')
1647
+ # of.write('IconFile: 1, 47, 47, 23, 23, mda_icons_144.png\n')
1648
+ of.write('IconFile: 1, 63, 63, 31, 31, mda_icons_192.png\n')
1649
+
1650
+ # 这里直接用m_pret 可以保证不会出现重叠的记录
1651
+ if not bdebug:
1652
+ if not m_pret is None and bhistory:
1653
+ pre_time = m_pret + timedelta(minutes=-1*int(6/2))
1654
+ endtime = m_pret + timedelta(minutes=1*int(6/2))
1655
+ elif not bhistory:
1656
+ pre_time = m_pret + timedelta(minutes=-1*int(6/2))
1657
+ endtime = m_pret + timedelta(minutes=30)
1658
+ pass
1659
+
1660
+
1661
+ timerangestr = 'TimeRange: ' + pre_time.strftime('%Y-%m-%dT%H:%M:%S') + ' ' + endtime.strftime('%Y-%m-%dT%H:%M:%S')
1662
+
1663
+ # 实时模式下,改时间一定要放到timerange后面
1664
+ if time_type == "BJT":
1665
+ pre_time = pre_time + timedelta(hours=8)
1666
+ endtime = endtime + timedelta(hours=8)
1667
+
1668
+ if bhistory:
1669
+ timerangestr = 'TimeRange: ' + pre_time.strftime('%Y-%m-%dT%H:%M:%S') + ' ' + endtime.strftime('%Y-%m-%dT%H:%M:%S')
1670
+
1671
+ of.write(timerangestr + '\n')
1672
+
1673
+ for nn in range(len(self.mesoinfo['meso'])):
1674
+ curitem = self.mesoinfo['meso'][nn]
1675
+ clon = curitem['lon']
1676
+ clat = curitem['lat']
1677
+ lbstr=''
1678
+ icon_index=0
1679
+ baseindex=0
1680
+ if curitem['base'] > 1500: # 米
1681
+ baseindex = 0
1682
+ else:
1683
+ baseindex = 1*4
1684
+ '''
1685
+ 关于风切变的单位换算问题
1686
+ https://www.theweatherprediction.com/habyhints/193/
1687
+ The wind speed is 20 knots at 850 mb while the wind speed is 30 knots at 700 mb. The distance between 850 and 700 mb is 1,600 meters. What is the value of wind shear?
1688
+
1689
+ The change in wind speed is 30 - 20 = 10 knots = 5 m/s
1690
+ The change is distance is 1,600 m
1691
+
1692
+ The change in wind speed over distance = 5/1,600 s^-1 = 0.003125 s^-1
1693
+
1694
+ '''
1695
+ shear_ms = curitem['ave_shear'] * (curitem['azi_diam'] + curitem['radial_diam'])/2/1000.0/2
1696
+ mrank = self.get_mda_rank(curitem['range']/1000,shear_ms)
1697
+
1698
+ if mrank is None:
1699
+ continue
1700
+ else:
1701
+ icon_index = baseindex + mrank
1702
+
1703
+ mlevel =''
1704
+ if mrank==1:
1705
+ mlevel = '弱切变'
1706
+ elif mrank == 2:
1707
+ mlevel = '弱中气旋'
1708
+ elif mrank == 3:
1709
+ mlevel = '中等强度中气旋'
1710
+ elif mrank == 4:
1711
+ mlevel = '强中气旋'
1712
+
1713
+ mtype='未知'
1714
+ if curitem['feature_type'] == 1:
1715
+ mtype = '中气旋'
1716
+ elif curitem['feature_type'] == 2:
1717
+ mtype = '3D相关切变'
1718
+ elif curitem['feature_type'] == 3:
1719
+ mtype = '非相关切变'
1720
+
1721
+ lbstr=''
1722
+ lbstr += r'数据来源: ROSE2中气旋识别产品\n'
1723
+ lbstr += r'所属风暴编号: %s\n'%curitem['storm_id_char']
1724
+ lbstr += r'中气旋编号: %s\n'%curitem['feature_id_char']
1725
+ lbstr += r'类型: %s\n'%mtype
1726
+ lbstr += r'中气旋所在方位/距离: %.1f度 / %.1f 公里\n'%(curitem['azi'],curitem['range']/1000.0)
1727
+ lbstr += r'强度等级: %s\n'%mlevel
1728
+ lbstr += r'中气旋底高: %.1f 公里\n'%(curitem['base']/1000.0)
1729
+ lbstr += r'中气旋顶高: %.1f 公里\n'%(curitem['top']/1000.0)
1730
+ lbstr += r'中气旋沿径向直径: %.1f 公里\n'%(curitem['radial_diam']/1000.0)
1731
+ lbstr += r'中气旋沿方位直径: %.1f 公里\n'%(curitem['azi_diam']/1000.0)
1732
+ lbstr += r'平均切变值: %.1f (E-3/S)\n'%(curitem['ave_shear'])
1733
+
1734
+ if shear_ms > 25:
1735
+ lbstr += r'===============提醒============\n换算后的切变值超过了25米/秒\n可能是很强的中气旋或者是错误数据\n可能是存在速度模糊对PUP算法的影响。\n'
1736
+
1737
+ of.write('Icon: %.4f,%.4f,0,1,%d,"%s"\n'%(clat,clon,icon_index, lbstr))
1738
+ pass
1739
+ of.write('End:\n\n')
1740
+
1741
+ of.close()
1742
+ print(outpath_gr2 + os.sep + outname_gr2 + ' saved!')
1743
+ return outpath_gr2 + os.sep + outname_gr2
1744
+ except:
1745
+ return False
1746
+
1747
+
1748
+ # 将tvs的信息转换为gr2格式
1749
+ def trans_tvs_gr2(self,outpath_gr2=None,outname_gr2=None,m_pret=None,time_type='UTC',bhistory=False,bdebug=False):
1750
+
1751
+ if self.tvsinfo is None:
1752
+ print('tvsinfo is None, please run read_tvs first')
1753
+ return False
1754
+
1755
+ try:
1756
+ # duration 代表该时次产品的可视时间段
1757
+ if not os.path.exists(outpath_gr2):
1758
+ os.makedirs(outpath_gr2)
1759
+
1760
+
1761
+ if outpath_gr2 is None or outname_gr2 is None:
1762
+ print('outpath_gr2 and outname_gr2 params should be set')
1763
+ return False
1764
+
1765
+ if not os.path.exists(outpath_gr2):
1766
+ try:
1767
+ os.makedirs(outpath_gr2)
1768
+ except:
1769
+ print(outpath_gr2 + ' not exist, and fail to create this path!')
1770
+ return False
1771
+
1772
+ of = open(outpath_gr2 + os.sep + outname_gr2,'wt',encoding='GBK')
1773
+
1774
+ # 单个文件就不用信息头了
1775
+ if bdebug and not bhistory:
1776
+ of.write('Title: ROSE2龙卷涡旋特征识别产品\n')
1777
+ of.write('Refreshseconds: 30\n')
1778
+ of.write('Threshold: 500\n')
1779
+ of.write('Font: 1, 20, 1, "Courier New"\n')
1780
+ of.write('IconFile: 1, 51, 51, 26, 26, lsr_icons_192.png\n')
1781
+
1782
+ # 这里直接用m_pret 可以保证不会出现重叠的记录
1783
+ if not bdebug:
1784
+ if not m_pret is None and bhistory:
1785
+ pre_time = m_pret + timedelta(minutes=-1*int(6/2))
1786
+ endtime = m_pret + timedelta(minutes=1*int(6/2))
1787
+ elif not bhistory:
1788
+ pre_time = m_pret + timedelta(minutes=-1*int(6/2))
1789
+ endtime = m_pret + timedelta(minutes=30)
1790
+ pass
1791
+
1792
+
1793
+ timerangestr = 'TimeRange: ' + pre_time.strftime('%Y-%m-%dT%H:%M:%S') + ' ' + endtime.strftime('%Y-%m-%dT%H:%M:%S')
1794
+
1795
+ # 实时模式下,改时间一定要放到timerange后面
1796
+ if time_type == "BJT":
1797
+ pre_time = pre_time + timedelta(hours=8)
1798
+ endtime = endtime + timedelta(hours=8)
1799
+
1800
+ if bhistory:
1801
+ timerangestr = 'TimeRange: ' + pre_time.strftime('%Y-%m-%dT%H:%M:%S') + ' ' + endtime.strftime('%Y-%m-%dT%H:%M:%S')
1802
+
1803
+ of.write(timerangestr + '\n')
1804
+
1805
+ for nn in range(len(self.tvsinfo['tvs'])):
1806
+ curitem = self.tvsinfo['tvs'][nn]
1807
+ clon = curitem['lon']
1808
+ clat = curitem['lat']
1809
+ curtype = '未知'
1810
+ if curitem['type'] == 1:
1811
+ curtype = 'TVS'
1812
+ elif curitem['type'] == 2:
1813
+ curtype = 'ETVS'
1814
+ icon_index=1
1815
+
1816
+ lbstr=''
1817
+ lbstr += r'数据来源: ROSE2龙卷涡旋识别产品\n'
1818
+ lbstr += r'所属风暴编号: %s\n'%curitem['storm_id_char']
1819
+ lbstr += r'类型: %s\n'%curtype
1820
+ lbstr += r'TVS所在方位/距离: %.1f度 / %.1f 公里\n'%(curitem['azi'],curitem['range']/1000.0)
1821
+
1822
+ lbstr += r'TVS底高: %.1f 公里\n'%(curitem['base']/1000.0)
1823
+ lbstr += r'TVS顶高: %.1f 公里\n'%(curitem['top']/1000.0)
1824
+ lbstr += r'TVS厚度: %.1f 公里\n'%(curitem['depth']/1000.0)
1825
+ lbstr += r'低层速度差LLDV: %.1f 米/秒\n'%(curitem['lldv']) # *1.609344/3.6
1826
+ lbstr += r'最大速度差MXDV: %.1f 米/秒\n'%(curitem['mxdv']) # *1.609344/3.6
1827
+ lbstr += r'最大速度差所在高度: %.1f 公里\n'%(curitem['hmdv']/1000.0)
1828
+
1829
+ lbstr += r'最大切变值MASHR: %.1f (E-3/S)\n'%(curitem['max_shear'])
1830
+ lbstr += r'最大切变值所在高度: %.1f 公里\n'%(curitem['h_max_shear']/1000.0)
1831
+
1832
+ if curtype=='ETVS':
1833
+ icon_index=20
1834
+ of.write('Icon: %.4f,%.4f,0,1,%d,"%s"\n'%(clat,clon,icon_index, lbstr))
1835
+ else:
1836
+ icon_index=11
1837
+ of.write('Icon: %.4f,%.4f,0,1,%d,"%s"\n'%(clat,clon,icon_index, lbstr))
1838
+
1839
+ of.write('End:\n\n')
1840
+
1841
+ of.close()
1842
+ print(outpath_gr2 + os.sep + outname_gr2 + ' saved!')
1843
+ return outpath_gr2 + os.sep + outname_gr2
1844
+ except:
1845
+ return False
1846
+
1847
+
1848
+ # 将hail的信息转换为gr2格式
1849
+ def trans_hda_gr2(self,outpath_gr2=None,outname_gr2=None,m_pret=None,time_type='UTC',bhistory=False,bdebug=False):
1850
+
1851
+ if self.hailinfo is None:
1852
+ print('hailinfo is None, please run read_hda first!')
1853
+ return False
1854
+
1855
+ try:
1856
+ # duration 代表该时次产品的可视时间段
1857
+ if not os.path.exists(outpath_gr2):
1858
+ os.makedirs(outpath_gr2)
1859
+
1860
+
1861
+ if outpath_gr2 is None or outname_gr2 is None:
1862
+ print('outpath_gr2 and outname_gr2 params should be set')
1863
+ return False
1864
+
1865
+ if not os.path.exists(outpath_gr2):
1866
+ try:
1867
+ os.makedirs(outpath_gr2)
1868
+ except:
1869
+ print(outpath_gr2 + ' not exist, and fail to create this path!')
1870
+ return False
1871
+
1872
+ of = open(outpath_gr2 + os.sep + outname_gr2,'wt',encoding='GBK')
1873
+
1874
+ # 单个文件就不用信息头了
1875
+ if bdebug and not bhistory:
1876
+ of.write('Title: ROSE2冰雹指数产品\n')
1877
+ of.write('Refreshseconds: 30\n')
1878
+ of.write('Threshold: 500\n')
1879
+ of.write('Font: 1, 20, 1, "Courier New"\n')
1880
+ # of.write('IconFile: 1, 37, 37, 18, 18, hail_icons_144.png\n')
1881
+ of.write('IconFile: 1, 51, 51, 26, 26, hail_icons_192.png\n')
1882
+
1883
+ # 这里直接用m_pret 可以保证不会出现重叠的记录
1884
+ if not bdebug:
1885
+ if not m_pret is None and bhistory:
1886
+ pre_time = m_pret + timedelta(minutes=-1*int(6/2))
1887
+ endtime = m_pret + timedelta(minutes=1*int(6/2))
1888
+ elif not bhistory:
1889
+ pre_time = m_pret + timedelta(minutes=-1*int(6/2))
1890
+ endtime = m_pret + timedelta(minutes=30)
1891
+ pass
1892
+
1893
+
1894
+ timerangestr = 'TimeRange: ' + pre_time.strftime('%Y-%m-%dT%H:%M:%S') + ' ' + endtime.strftime('%Y-%m-%dT%H:%M:%S')
1895
+
1896
+ # 实时模式下,改时间一定要放到timerange后面
1897
+ if time_type == "BJT":
1898
+ pre_time = pre_time + timedelta(hours=8)
1899
+ endtime = endtime + timedelta(hours=8)
1900
+
1901
+ if bhistory:
1902
+ timerangestr = 'TimeRange: ' + pre_time.strftime('%Y-%m-%dT%H:%M:%S') + ' ' + endtime.strftime('%Y-%m-%dT%H:%M:%S')
1903
+
1904
+ of.write(timerangestr + '\n')
1905
+
1906
+ for nn in range(len(self.hailinfo['hail'])):
1907
+ curitem = self.hailinfo['hail'][nn]
1908
+ clon = curitem['lon']
1909
+ clat = curitem['lat']
1910
+
1911
+ lbstr=''
1912
+ lbstr=r'数据来源: ROSE2冰雹指数产品\n'
1913
+ lbstr += r'冰雹编号: %s\n'%curitem['hail_id_char']
1914
+ lbstr += r'大冰雹概率: %d%%\n'%curitem['psh']
1915
+ lbstr += r'冰雹概率: %d%%\n'%curitem['ph']
1916
+ lbstr += r'最大冰雹尺寸估计: %.1f 厘米 或 %.1f 英寸\n'%(curitem['hsize'],curitem['hsize']/2.54,)
1917
+
1918
+ icon_index=0
1919
+ hsize_inch = curitem['hsize']/2.54
1920
+
1921
+ if hsize_inch < 1:
1922
+ icon_index = 1
1923
+ elif hsize_inch < 2:
1924
+ icon_index = 5
1925
+ elif hsize_inch < 3:
1926
+ icon_index = 9
1927
+ elif hsize_inch < 4:
1928
+ icon_index = 13
1929
+ elif hsize_inch >= 4:
1930
+ icon_index = 17
1931
+ else:
1932
+ icon_index = 1
1933
+ if curitem['ph'] >= 30:
1934
+ of.write('Icon: %.4f,%.4f,0,1,%d,"%s"\n'%(clat,clon,icon_index, lbstr))
1935
+
1936
+ of.write('End:\n\n')
1937
+
1938
+ of.close()
1939
+ print(outpath_gr2 + os.sep + outname_gr2 + ' saved!')
1940
+ return outpath_gr2 + os.sep + outname_gr2
1941
+ except:
1942
+ return False
1943
+
1944
+ if __name__ == "__main__":
1945
+ rootpath = '/Users/wenjianzhu/Library/CloudStorage/OneDrive-个人/PythonCode/MyWork/nmc_met_radar/testdata/rose_product'
1946
+ staid = 'Z9852'
1947
+ # filepath = 'D:\Downloads\CMADAAS\pup\Z9852\STI'
1948
+ outpath_gr2 = 'output/gr2data/sti' + os.sep + staid
1949
+ if not os.path.exists(outpath_gr2):
1950
+ os.makedirs(outpath_gr2)
1951
+
1952
+ decoder = READ_ROSE()
1953
+
1954
+ # # ppi zdr
1955
+ # filepath_ppi = '/Users/wenjianzhu/Downloads/CMADAAS/pup/Z9852/SS'
1956
+
1957
+ # # ss
1958
+ # filepath_ss = rootpath + os.sep + staid + os.sep + 'SS'
1959
+ # filename_ss = 'Z_RADR_I_Z9852_20230421164954_P_DOR_CDD_SS_NUL_300_NUL_FMT.bin'
1960
+ # allresult_ss = decoder.read_ss(filepath_ss, filename_ss)
1961
+
1962
+
1963
+ # sti
1964
+ filepath_sti = rootpath + os.sep + staid + os.sep + 'STI'
1965
+ filename_sti = 'Z_RADR_I_Z9852_20230421164954_P_DOR_CDD_STI_NUL_300_NUL_FMT.bin'
1966
+ allresult_sti = decoder.read_sti(filepath_sti, filename_sti)
1967
+ outname_sti = 'pls_sti.txt'
1968
+ decoder.trans_sti_gr2(outpath_gr2,outname_sti,time_type='BJT',bdebug = True)
1969
+
1970
+
1971
+ # meso
1972
+ filepath_meso = rootpath + os.sep + staid + os.sep + 'M'
1973
+ filename_meso = 'Z_RADR_I_Z9852_20230421135042_P_DOR_CDD_M_NUL_200_NUL_FMT.bin'
1974
+ allresult_meso = decoder.read_mda(filepath_meso, filename_meso)
1975
+ outname_meso = 'pls_meso.txt'
1976
+ decoder.trans_mda_gr2(outpath_gr2,outname_meso,time_type='BJT',bdebug = True)
1977
+
1978
+ # tvs
1979
+ filepath_tvs = rootpath + os.sep + staid + os.sep + 'TVS'
1980
+ filename_tvs = 'Z_RADR_I_Z9852_20230421201451_P_DOR_CDD_TVS_NUL_200_NUL_FMT.bin'
1981
+ allresult_tvs = decoder.read_tvs(filepath_tvs, filename_tvs)
1982
+ outname_tvs = 'pls_tvs.txt'
1983
+ decoder.trans_tvs_gr2(outpath_gr2,outname_tvs,time_type='BJT',bdebug = True)
1984
+
1985
+
1986
+ # hail
1987
+ filepath_hail = rootpath + os.sep + staid + os.sep + 'HI'
1988
+ filename_hail = 'Z_RADR_I_Z9852_20230421181642_P_DOR_CDD_HI_NUL_200_NUL_FMT.bin'
1989
+ allresult_hail = decoder.read_hda(filepath_hail, filename_hail)
1990
+ outname_hail = 'pls_hail.txt'
1991
+ decoder.trans_hda_gr2(outpath_gr2,outname_hail,time_type='BJT',bdebug = True)
1992
+
1993
+ # %%