简介:文档详细说明了CINRAD SA/SB和CA/CB型天气雷达基数据的二进制格式,面向气象雷达数据解析、科研与软件二次开发人员。内容系统梳理了雷达基数据的存储与排列方式,包括28字节雷达信息头、径向数据收集时间、儒略日、方位角、仰角、体扫模式、各数据指针字段,以及反射率、速度、谱宽数据的距离库长与库数等关键参数,并给出SA/SB与CA/CB两种型号在径向数据长度、距离库数等方面的差异说明。资源为1个doc格式文件,压缩包大小92KB,以字节对照表形式完整呈现各字段长度与编码规则,可直接用于指导雷达基数据的读取与解码。目前已有2799人学习下载,对需要编写雷达基数据解析程序、处理CINRAD雷达产品数据的研究与工程人员具有较高的参考价值。
1. 雷达基数据:气象雷达数据处理的第一道门槛
很多做雷达数据处理的朋友,看图用得滚瓜烂熟,一旦拿到一份几百MB的基数据文件,就卡在第一步:怎么读?文件扩展名是.bin、.raw还是.dat都无所谓,关键是里面这些字节怎么变成一根根径向的反射率、速度和谱宽。雷达基数据格式说明解决的就是这个问题——把二进制文件里的站点参数、径向头、距离库采样值一点一点还原出来。下面我按格式概念、文件结构、解析流程、踩坑排查的顺序讲,覆盖需要写解析程序、做质控算法、或者把基数据转成网格的工程师。读完你至少能自己写出一个能用的最小编解码器。
2. 从原始采样到基数据:先弄懂你在解析什么
2.1 雷达数据链路里,基数据处于哪一层
雷达发射脉冲后,回波信号经过接收机变成模拟中频,再数字化成I/Q数据。I/Q数据是信号处理器的原材料,里面包含幅度和相位信息,数据量巨大,一般不会直接给到算法工程师。信号处理器对同一距离库上的一组脉冲做FFT或脉冲对处理,算出零阶矩、一阶矩和二阶矩,分别对应反射率、径向速度和谱宽。这些物理量按“方位角递增、距离库递增”的方式排成径向,就形成了基数据。基数据之上再做坐标转换、插值、拼图,才是业务上看到的PPI、RHI回波图。
理解这一层之后,你就知道基数据不是“原始”数据,它已经经过了信号处理。这也是为什么很多质控算法要自己读基数据——产品图已经丢了细节,只有基数据还保留着每个径向、每个距离库的原始估计。我在实际工作中,经常遇到用户拿着产品图来问“这个多强的回波是不是地物”,这时候只能把基数据调出来,用反射率和谱宽联合判断。这个场景决定了基数据的解析必须精确到每一个径向头字段,而不是只把数值读出来就完事。
| 数据层 | 内容 | 使用场景 | | I/Q原始数据 | 采样点幅度/相位 | 信号处理算法研究 | | 基数据 | 反射率/速度/谱宽 | 质控、雷达算法开发 | | 产品数据 | 各类回波图/风场图 | 业务预报、展示 |
2.2 径向、距离库、仰角层:绕不开的三个概念
一个径向(radial)是雷达天线在某个方位角发射一个脉冲后,沿射线方向按距离采样得到的一串数据。每个采样点叫一个距离库(range bin),相邻两个库的中心距离差就是距离分辨率,常见的有250米、750米、1000米几种。库的数量乘以分辨率,决定了最大探测距离。比如库数1010、分辨率250米,最大探测距离就是252公里左右。注意不是所有库都有效,有些距离上噪声很大,基数据里会用特殊值标记。
仰角层(elevation layer)是天线在固定仰角下做一圈方位扫描收集到的所有径向。雷达按预定的体扫模式(VCP)依次扫描多个仰角,就得到一个体扫。体扫模式决定了一共有几层仰角、每层多长时间扫完、相邻径向之间角度差多少。常见VCP有9个仰角、14个仰角等,低层仰角扫描慢、径向多,高层仰角扫描快、径向少。解析基数据时,你本质上是按仰角层一层层读,每个仰角层里再读出若干径向。如果格式里没有给出每个仰角层的径向数,就需要根据扫描角速度和方位角步进自己推算。
| 名称 | 英文 | 含义 | | 径向 | radial | 固定方位角上沿距离轴的一串采样 | | 距离库 | range bin | 径向里的单个采样点,对应一个距离门 | | 仰角层 | elevation layer | 同一仰角下所有径向的集合 | | 体扫模式 | VCP | 一个完整体积扫描的仰角和径向规划 |
2.3 基数据里的三个物理量:反射率、径向速度、谱宽
反射率的单位是dBZ,反映目标物对雷达波的后向散射强度,是最常用的量。降水回波一般从5 dBZ往上,强对流可以到60 dBZ以上。径向速度单位是m/s,表示目标物沿雷达径向上的运动速度,正值表示远离雷达,负值表示靠近雷达。谱宽是速度谱展宽,单位也是m/s,值越大说明雷达采样体积内目标物速度分布越宽,常用于识别地物、湍流和阵风锋。
这三个量在基数据文件里通常都有各自的通道。我见过不少格式把反射率放第一个通道,速度第二个,谱宽第三个。有些雷达还会多存一个差分反射率或其他双偏振量,通道顺序和编码差异很大。读任何一个新格式,第一件事就是确认通道顺序,这是最容易踩坑的地方。一个土办法是:如果读出来的“反射率”曲线在一段距离上完全平坦且数值极端,那多半是通道顺序读错了,把谱宽当成了反射率。
2.4 扫描模式与径向数量:从文件头提前算一遍
雷达有两种基本扫描模式:PPI(平面位置扫描)和RHI(距离高度扫描)。PPI是固定仰角扫方位,RHI是固定方位角扫仰角。基数据里最常见的是PPI。PPI又分全方位扫描和扇区扫描。全方位扫描一圈角度从0到360度,扇区扫描只在一个角度范围内来回扫,比如从120度到240度。解析前先看文件头里的扫描模式字段是哪种,然后估算每层径向数量:径向数约等于扫描角度范围除以方位角步进,再加1或2。
体扫的地基数据里,每个仰角层的径向数往往不同,低仰角层为了获得更好的方位分辨率,扫描更慢,径向更多;高仰角层扫描很快,径向数可能是低层的一半。如果文件里没有显式写每层的径向数,我就用上面这个估算值做初步校验。比如一个360度扫描的PPI,方位角步进1度,那径向数应该在360到365之间。如果实际读到500根径向,说明步进字段读错了或者文件里有重复的径向。这种前置校验能省下大量排查时间。
3. 一个可用的雷达基数据文件结构:头部、数据块与径向记录
3.1 文件级结构:魔数、版本号、站点信息
几乎所有雷达基数据文件都遵循“文件头 + 元数据块 + 径向数据块”的结构。文件头用来识别格式和定标。以我接触过的几种常见格式为例,开头几字节一般是一个魔数,比如“RDAX”“RADA”之类,用来告诉解析程序“这是雷达基数据文件”。紧接着是版本号,格式更新时版本号会变,解析器必须根据版本号走不同分支。有些老格式没有魔数,开头直接就是版本号,这就要靠扩展名和文件大小辅助判断。
文件头里还会有站点编号、站点经纬度、天线海拔、雷达波长和发射机参数。这些参数对后续速度解模糊、距离校正很重要。经纬度通常用double存储,单位是度;海拔用float或double,单位是米;波长单位一般写厘米。不少格式会把“数据块偏移量”放在固定位置,解析程序直接读出来跳转,不用扫描整个文件去找径向数据。如果偏移量是0,说明数据块紧跟文件头,这时要小心文件头里是否有变长字段,不能简单用固定长度跳转。
下面是我整理的一个常见文件头布局,字段名在不同雷达上会有差异,但基本离不开这些。偏移量、类型只代表一种典型实现,你拿到实际格式时要以文档为准。
| 偏移 | 字段 | 类型 | 说明 | | 0-3 | 魔数 | char[4] | 格式识别标识 | | 4-7 | 版本号 | uint32 | 格式版本,决定后续字段解释 | | 8-15 | 站点编号 | char[8] | 雷达站标识 | | 16-23 | 站点纬度 | double | 单位度,北纬为正 | | 24-31 | 站点经度 | double | 单位度,东经为正 | | 32-39 | 天线海拔 | double | 单位米 | | 40-43 | 波长 | float | 单位厘米 | | 44-47 | 数据块偏移 | uint32 | 第一个径向数据块的字节偏移 |
提示:这个布局只是示例。我在实际读一个陌生雷达文件时,会先打印前64字节的十六进制值,结合魔数和版本号反推字段边界,再拿文档逐项核对。这个方法比照着文档硬写更稳妥,因为文档偶尔会写错字段长度。
3.2 径向数据块:每个径向的字段与字节布局
文件头之后通常有一个“数据块描述区”,写着总共有多少仰角层、每个仰角层从哪个偏移开始、每个仰角层里有多少径向。然后就是真正的径向数据区。一个径向数据块由径向头和通道数据构成。径向头里最常见的字段是径向序号、方位角、仰角、距离库数、脉冲宽度、时间戳。距离库数告诉你要读多少个采样值;脉冲宽度影响回波强度和距离分辨率。时间戳有时分成日期和时间两个字段,有时是一个Unix时间戳,解析时要先确认。
通道数据区的布局有两种:一种是“一个径向内按通道顺序排列”,即先把反射率整条径向的值连续存完,再存速度,再存谱宽;另一种是“按距离库交替排列”,即第一个库的三个通道,然后第二个库。绝大多数气象雷达基数据用前一种,也就是按通道整存。如果你按交替排列去读,反射率数组会变得支离破碎,而且相邻值毫无物理关联。可以先看文档里的“数据组织”段落,没有文档就直接对比反射率曲线,整存格式应该是一条连续光滑的曲线。
径向头的字节布局我写一个典型例子,定长48字节,这个长度在不少雷达格式里出现过,但不要当作通用标准。
| 字段 | 类型 | 偏移 | 说明 | | 径向序号 | uint32 | 0 | 本仰角层内的序号 | | 数据长度 | uint32 | 4 | 通道数据总字节数 | | 仰角 | float | 8 | 单位度 | | 方位角 | float | 12 | 单位度 | | 距离库数 | uint32 | 16 | 有效采样点数 | | 时间戳 | uint32 | 20 | Unix时间或自定义绝对时间 | | 脉冲宽度 | float | 24 | 单位微秒 | | 垂直/水平波束宽度 | float[2] | 28 | 单位度 | | 系统增益等 | float[4] | 36 | 部分格式用于标定 |
径向头后面紧跟通道数据。物理量编码常见的是:反射率用uint8,缩放因子0.1 dBZ,偏移-32;径向速度用int16,缩放因子0.0625 m/s;谱宽用uint8,缩放因子0.0625 m/s。这里的“缩放因子”就是LSB代表多少物理量,“偏移”是0值对应的物理量。把这些写进代码前,建议先用一个径向手工算一两个值,确认量纲对得上。
3.3 脉冲宽度、PRF、最大不模糊速度:格式里怎么查
这几个参数通常写在文件头或每个径向头里,也有写在数据块描述区的。PRF(脉冲重复频率)决定最大不模糊速度和距离。最大不模糊速度由公式 Vmax = PRF × λ / 4 计算,λ为波长;最大不模糊距离由 Rmax = c / (2 × PRF) 计算。雷达设计时要在这两个量之间做权衡,PRF高则测速范围大但测距变短,PRF低则相反。很多格式会直接把Vmax和Rmax写进去,你不一定需要现场算。
脉冲宽度影响距离分辨率和最小可测信号。脉宽越长,灵敏度越高,但距离分辨率越差。格式里通常存脉宽数值,比如0.5微秒、1微秒、2微秒。距离分辨率大约是脉宽乘以光速的一半,再考虑匹配滤波。读基数据时,这两个参数至少要在解析日志里打印出来,因为后面做速度解模糊、处理双PRF扫描时都要用到。我见过一些格式把PRF放在径向头里,而不是文件头,这时每根径向的PRF都不同,尤其要注意。
我一般会在解析函数里加一段“参数合理性检查”:如果读出的PRF明显超出雷达物理范围,比如7000 Hz以上,或者Vmax小于3 m/s,那就说明字节序或字段偏移读错了。这种检查能帮忙在批量处理前发现问题,而不是等到画图时才发现整片回波朝向都不对。参数的合理性范围可以参考同类雷达的公开技术指标,但不能照搬,因为不同波段差别很大。
3.4 仰角层索引表与径向偏移:如何随机访问任意径向
很多格式在数据块描述区之后放一个“索引表”,为每个仰角层甚至每个径向记录偏移量。有索引表就不需要从头扫描。解析大量文件时,随机访问能省很多I/O。索引表通常长这样:每个仰角层记录起始偏移、径向数、起始方位角。也有的格式更细,给每一根径向记录偏移量。如果给你的是“每一层的起始偏移”,那层内径向的长度可以通过径向头里的“数据长度”字段累加,但前提是格式保证层内径向连续存放。
| 索引项 | 类型 | 说明 | | 仰角层起始偏移 | uint32 | 每层第一个径向的字节偏移 | | 仰角层径向数 | uint32 | 该层包含的径向数量 | | 层起始方位角 | float | 可选,用于快速定位扇区 |
遇到没有索引表的格式,只能按顺序读。这时不用频繁seek,从头到尾读一遍反而更快。我建议先把整个文件读进内存,再用偏移切片,因为磁盘随机读在机械硬盘上很慢。文件不大时(几百MB以内),一次性读入是常见做法。对于超过1GB的文件,可以用mmap按需读段。索引表的作用不只是访问方便,还能核对文件里实际径向数和头部描述是否一致,一旦对不上基本可以判定文件损坏或解析有误。
4. 把格式说明变成解析代码:读取一个径向的完整流程
4.1 用Python打开二进制文件,按头部信息定位径向块
明确了结构,解析就是用代码把头信息和径向块按字节读出来。我一般用Python的struct库处理定长字段,用numpy把通道数据整块读成数组。第一步是打开文件,按头部布局读取魔数、版本号、站点坐标和数据块偏移。
下面是一段最小读取示例,对应第3.1节的布局。注意struct.unpack的格式串用“<”指定小端字节序,因为大部分雷达基数据是小端存储。如果雷达厂商用了大端,后面所有读取都要换成“>”,这个问题在后面单独说。
import struct import numpy as np # 假设文件头布局:魔数4字节、版本号4字节、 # 纬度8字节、经度8字节、海拔8字节、波长4字节、数据块偏移4字节 def read_file_header(path): with open(path, 'rb') as f: header = f.read(48) # 先读定长头部 magic = header[0:4].decode('ascii', errors='ignore') version = struct.unpack('<I', header[4:8])[0] lat = struct.unpack('<d', header[8:16])[0] lon = struct.unpack('<d', header[16:24])[0] alt = struct.unpack('<d', header[24:32])[0] wavelength = struct.unpack('<f', header[32:36])[0] data_offset = struct.unpack('<I', header[36:40])[0] print(f'文件魔数: {magic}, 版本: {version}') print(f'站点纬度: {lat:.4f}, 经度: {lon:.4f}, 海拔: {alt:.1f}m') print(f'波长: {wavelength:.2f}cm, 数据块偏移: {data_offset}') return { 'magic': magic, 'version': version, 'lat': lat, 'lon': lon, 'alt': alt, 'wavelength': wavelength, 'data_offset': data_offset }代码里的要点是不要用f.read()从头读到尾,而是按偏移量跳转。很多格式在头部后面有变长字段,固定偏移更安全。另外,魔数decode时要用errors='ignore',否则遇到非ASCII字节会抛异常。这里的data_offset位于36字节,如果你手里的格式在文件头里多塞了字段,偏移要按实际文档调整,不能用这段代码直接套。
4.2 遍历一个仰角层,把反射率还原成物理量
定位到数据块后,读取数据块描述区,拿到仰角层数和每个仰角层的径向数。然后进入径向数据区,按径向头字段读取长度、方位角、仰角、距离库数等,再读通道数据。下面这段代码演示怎么解析一个仰角层的全部径向,并把反射率通道转换成dBZ。为了简洁,省略了径向头的完整解析,只保留关键字段。
def read_layer(f, layer_offset, radial_count, range_bin_count): radials = [] f.seek(layer_offset) for i in range(radial_count): radial_header = f.read(48) # 按第3.2节的布局解析径向头 _, data_len, elev, azim, num_bins, _, pulse_width = struct.unpack( '<IIffIhH', radial_header[:24] ) # 读取反射率通道:uint8数组 # 这里假设通道数据区按 反射率/速度/谱宽 顺序整存,每个通道num_bins个值 raw_ref = np.frombuffer( f.read(num_bins), dtype=np.uint8 ) # 跳过速度和谱宽通道,因为示例只解析反射率 f.seek(num_bins * 2, 1) # 速度 int16 f.seek(num_bins * 1, 1) # 谱宽 uint8 # 特殊值:0无回波,1缺测,2超量程;>=3是正常反射率 valid_mask = raw_ref >= 3 ref_dbz = np.full_like(raw_ref, np.nan, dtype=np.float64) ref_dbz[valid_mask] = raw_ref[valid_mask] * 0.1 - 32.0 radials.append({ 'elevation': elev, 'azimuth': azim, 'range_bins': num_bins, 'reflectivity': ref_dbz }) return radials代码里有几个地方要说明。径向头struct.unpack的格式串和字段类型必须和文档严格一致,特别是有符号还是无符号。我见过不少人把int16写成uint16,导致速度负值全变成大正数。读取速度通道时,如果速度用int16存储,f.read返回的是字节串,np.frombuffer会按内存顺序解析,不需要再关心小端,因为本机通常也是小端;如果要跨平台,可以先按字节串转int16。
反射率转换时,我先创建一个全NaN数组,再用valid_mask填充有效值。这样后面做统计、画图时,NaN自然不算数。如果你直接拿特殊值0、1、2去算,反射率会多出一堆-32、-31和“超量程”的值。另外要注意,num_bins可能不等于range_bin_count,这里以径向头里的实际值为准,不要用外部传入的理论值。
4.3 跨平台与性能:批量读取时的两个注意点
雷达基数据文件通常不只一个,遇到几十上百个体扫文件时,性能就很重要。第一个注意点是np.frombuffer出来的数组是只读视图,如果后续要原地修改,需要先copy。第二个注意点是如果文件是大端字节序,不要盲目对整个数组做byteswap,因为特殊值掩膜也会被换掉。正确做法是先按原始类型读出数组,判断字节序后再用数组方法转换。
批量读取时,尽量避免逐径向f.read小块数据。小段读取会让磁盘频繁寻道,尤其机械硬盘上慢得明显。我一般把整个文件用numpy.memmap打开,或者先用open读成bytes,再在内存里切片。下面这段是使用bytes切片的思路:
with open('radar_base_data.bin', 'rb') as f: buf = f.read() # 直接用buffer切片,等效于seek+read layer_offset = 4096 # 假设从数据块描述区得到的偏移 radial_count = 360 num_bins = 1000 for i in range(radial_count): start = layer_offset + i * (48 + num_bins * 3) radial_header = buf[start:start+48] # 这里就能省掉f.seek,更适合连续读多个径向这种写法的前提是,你知道每个径向的固定总长度。如果距离库数不固定,还是要逐个径向解析。性能优化的目标不是追求极致的运行速度,而是不要让代码在大量文件上跑一晚上才报错。先保证正确,再优化速度。
4.4 解析过程中的三个必检项:版本、长度、量纲
读完第一个径向之后,不要急着批量跑。我习惯先做三个检查。第一,打印版本号和扫描模式,确认代码走到了对的格式分支;第二,检查第一个径向的方位角和仰角是否在物理范围内,比如方位角0到360、仰角在负几度到正几度,如果出现几千度,说明径向头偏移读错;第三,把反射率数组画一条曲线,看距离库上是不是连续变化,正常的层状云回波应该是平滑的,如果相邻库之间忽大忽小、出现大量-32,可能是数据通道顺序搞反了。
这一步我称之为“单径向冒烟测试”。很多人在写完解析器后直接对几百个文件批量运行,结果输出的数据全是垃圾,再回头排查就很费劲。先拿一个径向验证,基本能定位大部分格式问题。例如某一次我读一个新雷达格式,反射率曲线整体正常但边界库全部是0,排查后发现是文档里“距离库数”写的是理论库数,实际文件里存的数据少了两百个库,这批径向末尾是补0。这种问题靠冒烟测试一眼就能看出来。
5. 解析雷达基数据的常见问题与排查手册
5.1 径向长度对不上:头文件的距离库数与实际字节数不一致
现象:用距离库数乘以通道数去读数据,程序抛异常或读出的反射率数组末尾多出一堆0。尤其在一个文件读到后半段时,径向偏移越差越远,最后直接崩溃。
原因:多数是文档和实现不一致。径向头里的“距离库数”可能是天线扫描时的“最大库数”,也就是按最大探测距离计算的库数,而文件里为了减少存储量,只写了有效数据的实际库数。也有相反的情况,文件里有几个保留库用于后续扩展,实际有效库数在另一个字段里。
解决:定位径向头里所有整型字段,逐个打印,找到“对于这个径向,我的数据区到底有多少字节”的那个字段。它的范围应该和实际读取字节数吻合。为了提高效率,可以在解析前随机抽取10个径向,分别按候选字段读取,看哪个字段算出的总字节数能和文件末尾位置对得上。这个方法比反复看文档可靠得多。
5.2 反射率出现负值或离谱大值:缩放因子和偏移没还原
现象:解析出的反射率值不是正常的dBZ范围,离散值集中在-32、-31、95或者255,画图时色标一片蓝或一片红。
原因:直接把原始存储值当成物理量用了,或者没有对特殊值掩膜。反射率在多数格式里以uint8存储,为了压缩动态范围,会乘10再加偏移。比如真实-32 dBZ对应0,95 dBZ对应255。特殊值0、1、2分别表示无回波、缺测、超量程,如果换算前不过滤,这些值也会进到结果里。
解决:先看从文件读出来的原始uint8数组的分布。如果集中在0到2,说明还没到换算就看错了;如果分布在0到255,再按公式还原。最好把缩放和偏移写成一个独立的转换函数,输入原始数组,输出物理量数组,特殊值一律填NaN。这样后续统计和绘图不会受污染。
5.3 扇区扫描当成全方位扫描,方位角跳变
现象:用极坐标画图时,回波方位角有缺口或重叠,特别是在扇区边缘,回波位置明显错乱。细看数组,方位角一会儿从170度跳到190度,一会儿又跳回170度。
原因:雷达在扇区扫描模式下,天线在固定角度范围内来回摆,每次扫描的起点和终点不固定,所以径向记录的方位角不是单调递增的。解析代码如果假设方位角从0到360均匀分布,用固定网格插值,就会产生错乱。
解决:读取文件头或径向描述里的“扫描模式”字段;如果明确是扇区扫描,不要把径向存成二维规则网格,而是保留每个径向自己的方位角,用逐径向绘图或三角插值。画图时用散点或三角形网格,而不是直接pcolormesh在均匀网格上。
5.4 字节序不对,小数点前移了几位
现象:读出的版本号是16777216而不是1,经纬度是几千万度,波长是天文数字。所有“看起来合理”的数值都不合理。
原因:雷达厂商在导出格式时可能使用小端字节序,而解析代码用了大端,或者反过来。更坑的是,同一厂家的不同版本文件,字节序竟然会变。我遇到过一次固件升级后导出的文件字节序从大端变成小端,前面所有历史解析代码全部翻车。
解决:先用一个已知字段试读。比如魔数,如果是字符,应该能读出可读ASCII;版本号,应该在1到几十之间。用struct.unpack同时尝试‘<I’和‘>I’读取版本号,哪个落在合理区间就用哪个。然后把字节序作为解析器构造参数传进去,不要写死在函数里。这样以后遇到字节序不同的新版本,只需要改配置。
5.5 换个文件就乱码:版本号分支没做好
现象:同一部雷达,上个月导出的文件能正常解析,这个月导出的文件读出来全是乱码,或者通道对不上。
原因:雷达处理软件升级后,格式版本号变了,字段偏移、通道顺序或编码方式有调整。解析器没有按版本号分支,还是用旧layout去读新文件。
解决:在解析入口读取版本号,按版本号分发到不同layout定义。遇到不认识的版本号,宁可抛异常也不要硬猜。我一般会把版本号打印在日志第一行,这样跑批量任务时,一旦输出异常,能第一时间发现混入了新版本文件。格式差异可能很小,比如径向头从48字节变成52字节,不分支的话,后面全错。
6. 把格式吃透后的进阶用法:用径向数据画出一张扇形图
解析出径向数据后,最直观的验证就是画扇形图。这里给一个常用套路:先确认径向数量、每个径向的距离分辨率,再把方位角转成弧度,最后用极坐标投影画图。注意反射率矩阵的形状是(radial_count, range_bin_count),而极坐标网格的theta和r形状是(range_bin_count, radial_count),所以要对反射率转置。
import numpy as np import matplotlib.pyplot as plt # radials 是第4.2节返回的数据 azimuths_deg = np.array([r['azimuth'] for r in radials]) reflectivity = np.array([r['reflectivity'] for r in radials]) # 过滤缺测径向 azimuths_valid = np.deg2rad(azimuths_deg) range_max_km = 150.0 distance = np.linspace(0, range_max_km, reflectivity.shape[1]) # 生成极坐标网格 theta, r = np.meshgrid(azimuths_valid, distance) # reflectivity是(radial, range),转置成(range, radial) data = reflectivity.T fig = plt.figure(figsize=(8, 8)) ax = fig.add_subplot(111, projection='polar') mesh = ax.pcolormesh(theta, r, data, cmap='jet', vmin=-10, vmax=60) ax.set_theta_zero_location('N') ax.set_theta_direction(-1) plt.colorbar(mesh, ax=ax, label='Reflectivity (dBZ)') plt.title('PPI Reflectivity') plt.show()这段代码里有三个细节。一是set_theta_zero_location('N')把0度放到正北,set_theta_direction(-1)让方位角顺时针增加,符合雷达显示习惯。二是vmin和vmax根据天气雷达常见的反射率范围设置,也可以按需调整。三是如果径向数量少,pcolormesh画出的扇形会有明显的辐条感,可以考虑插值到均匀网格再画,但插值会增加平滑伪影,质量控制前我不建议过度插值。
有了扇形图做地基,后续还可以做方位角间插值、把极坐标转成经纬度网格,再和其他雷达拼图。我现在的习惯是,拿到一个新格式的基数据,先写一个最小编解码器,只打印文件头和第一根径向的信息,再画一张扇形图,确认物理量正常,才去完善批量处理和质控逻辑。格式说明书写得再清楚,也不如亲手解析一次来得踏实。希望帮到你。
本文还有配套的精品资源,点击获取