天气雷达拼图系统V3.0产品数据解析

文摘   2024-08-19 09:00   北京  

网友给发了一个天气雷达拼图数据文件,问MeteoInfo是否能打开并绘图。因为是二进制文件,没有数据文件格式说明文档是无法解析的。没找到正式的说明文档,不过在网上找到一个用Python解析数据文件的帖子(https://blog.csdn.net/weixin_45863084/article/details/140492948),试了一下应该是同一种数据文件。参考该贴写了一个MeteoInfoLab脚本来解析和绘制雷达拼图数据文件,MeteoInfoLab的脚本语言是Jython,和Python的语法一致,因此移植很方便。

雷达拼图二进制文件的格式比较简单,有一个256字节的文件头,后面就是等经纬度格点数据了。文件头里包含了数据文件产品描述信息、时间、格点分布等。用一个类 BinaryReader 来读取雷达拼图数据:

import structimport bz2
class BinaryReader: """ Radar data reader """ def __init__(self, buffer, little_endian=True): self.buffer = buffer self.index = 0 self.little_endian = little_endian self.read_header()
def seek(self, offset, origin=0): if origin == 0: # Set absolute position self.index = offset elif origin == 1: # Set relative to current position self.index += offset elif origin == 2: # Set relative to end of file self.index = len(self.buffer) + offset
def read_bytes(self, length): data = self.buffer[self.index:self.index + length] self.index += length return data
def read_string(self, length): fmt = '{}s'.format(length) value, = struct.unpack_from(fmt, self.buffer, self.index) value.replace('\x00', '').replace(' ', '') self.index += struct.calcsize(fmt) return value
def read_int(self): fmt = '<i' if self.little_endian else '>i' value, = struct.unpack_from(fmt, self.buffer, self.index) self.index += struct.calcsize(fmt) return value
def read_int16(self): fmt = '<h' if self.little_endian else '>h' value, = struct.unpack_from(fmt, self.buffer, self.index) self.index += struct.calcsize(fmt) return value
def read_header(self): """ Read data header """ self.label = self.read_string(4) # Data file ID self.version = self.read_string(4) # Data file version self.file_bytes = self.read_int() self.mosaic_id = self.read_int16() self.coordinate = self.read_int16() self.varname = self.read_string(8) self.description = self.read_string(64) BlockPos = self.read_int() BlockLen = self.read_int() self.TimeZone = self.read_int() self.year = self.read_int16() self.month = self.read_int16() self.day = self.read_int16() self.hour = self.read_int16() self.minute = self.read_int16() self.second = self.read_int16() self.radartime = datetime.datetime(self.year, self.month, self.day, self.hour, self.minute, self.second) ObsSeconds = self.read_int() ObsDates = self.read_int16() GenDates = self.read_int16() GenSeconds = self.read_int() self.lat_min = self.read_int() / 1000 self.lon_min = self.read_int() / 1000 self.lat_max = self.read_int() / 1000 self.lon_max = self.read_int() / 1000 cx = self.read_int() / 1000 cy = self.read_int() / 1000 # Read data information self.nX = self.read_int() # Number of data columns self.nY = self.read_int() # Number of data rows self.dx = self.read_int() / 10000 self.dy = self.read_int() / 10000 self.height = self.read_int16() self.compress = self.read_int16() self.num_of_radars = self.read_int() self.unZipBytes = self.read_int() self.scale = self.read_int16() unUsed = self.read_int16() RgnID = self.read_string(8) self.units = self.read_string(8) reserved = self.read_string(8)
def read_data(self): """ Read data bytes by skipping 256 bytes of data header """ if self.compress == 0: return self.buffer[256:] else: return self.unbzip2_decompress(self.buffer[256:])
def unbzip2_decompress(self, data): """ Decompress data with bz2 """ try: return bz2.decompress(data) except Exception as e: logger.error("Error decompressing bz2 data: {}".format(e)) return None

需要注意的是数据可能是bz2压缩过的,通过头文件中的 compress 信息判断,如果是数据有压缩需要进行解压。

BinaryReader类初始化时最重要的参数是数据文件中读出来的所有字节 buffer:

fn = 'D:/Temp/binary/radar/ACHN_CREF_20240718_234800.BIN'with open(fn, 'rb') as f:    buffer = f.read()
br = BinaryReader(buffer, little_endian=True)

然后用BinaryReader类对象br的read_data()函数读取数据(为Python字节数组),用numeric模块中的frombuffer函数将字节数组转为NDArray一维short数组(frombuffer函数在MeteoInfo 3.9.2版本才有),然后根据头文件中的nY, nX信息转为二维数据,还需要除以 scale并对Y轴反向得到雷达反射率数据,小于-20的值赋为nan不在图形中绘制。

br = BinaryReader(buffer, little_endian=True)data = br.read_data()data = np.frombuffer(data, dtype=dtype.short)data = data.reshape(br.nY, br.nX)data = data / br.scaledata = data[::-1]data[data<-20] = nan

头文件中包含了数据的经纬度范围,结合经纬度方向的格点数可以将数据的二维格点坐标lon, lat构建出来:

lon = np.linspace(br.lon_min, br.lon_max, br.nX)lat = np.linspace(br.lat_min, br.lat_max, br.nY)

剩下就是绘图了。

完整代码如下:

import structimport bz2
class BinaryReader: """ Radar data reader """ def __init__(self, buffer, little_endian=True): self.buffer = buffer self.index = 0 self.little_endian = little_endian self.read_header()
def seek(self, offset, origin=0): if origin == 0: # Set absolute position self.index = offset elif origin == 1: # Set relative to current position self.index += offset elif origin == 2: # Set relative to end of file self.index = len(self.buffer) + offset
def read_bytes(self, length): data = self.buffer[self.index:self.index + length] self.index += length return data
def read_string(self, length): fmt = '{}s'.format(length) value, = struct.unpack_from(fmt, self.buffer, self.index) value.replace('\x00', '').replace(' ', '') self.index += struct.calcsize(fmt) return value
def read_int(self): fmt = '<i' if self.little_endian else '>i' value, = struct.unpack_from(fmt, self.buffer, self.index) self.index += struct.calcsize(fmt) return value
def read_int16(self): fmt = '<h' if self.little_endian else '>h' value, = struct.unpack_from(fmt, self.buffer, self.index) self.index += struct.calcsize(fmt) return value
def read_header(self): """ Read data header """ self.label = self.read_string(4) # Data file ID self.version = self.read_string(4) # Data file version self.file_bytes = self.read_int() self.mosaic_id = self.read_int16() self.coordinate = self.read_int16() self.varname = self.read_string(8) self.description = self.read_string(64) BlockPos = self.read_int() BlockLen = self.read_int() self.TimeZone = self.read_int() self.year = self.read_int16() self.month = self.read_int16() self.day = self.read_int16() self.hour = self.read_int16() self.minute = self.read_int16() self.second = self.read_int16() self.radartime = datetime.datetime(self.year, self.month, self.day, self.hour, self.minute, self.second) ObsSeconds = self.read_int() ObsDates = self.read_int16() GenDates = self.read_int16() GenSeconds = self.read_int() self.lat_min = self.read_int() / 1000 self.lon_min = self.read_int() / 1000 self.lat_max = self.read_int() / 1000 self.lon_max = self.read_int() / 1000 cx = self.read_int() / 1000 cy = self.read_int() / 1000 # Read data information self.nX = self.read_int() # Number of data columns self.nY = self.read_int() # Number of data rows self.dx = self.read_int() / 10000 self.dy = self.read_int() / 10000 self.height = self.read_int16() self.compress = self.read_int16() self.num_of_radars = self.read_int() self.unZipBytes = self.read_int() self.scale = self.read_int16() unUsed = self.read_int16() RgnID = self.read_string(8) self.units = self.read_string(8) reserved = self.read_string(8)
def read_data(self): """ Read data bytes by skipping 256 bytes of data header """ if self.compress == 0: return self.buffer[256:] else: return self.unbzip2_decompress(self.buffer[256:])
def unbzip2_decompress(self, data): """ Decompress data with bz2 """ try: return bz2.decompress(data) except Exception as e: logger.error("Error decompressing bz2 data: {}".format(e)) return None

fn = 'D:/Temp/binary/radar/ACHN_CREF_20240718_234800.BIN'with open(fn, 'rb') as f: buffer = f.read()
br = BinaryReader(buffer, little_endian=True)data = br.read_data()data = np.frombuffer(data, dtype=dtype.short)data = data.reshape(br.nY, br.nX)data = data / br.scaledata = data[::-1]data[data<-20] = nan
lon = np.linspace(br.lon_min, br.lon_max, br.nX)lat = np.linspace(br.lat_min, br.lat_max, br.nY)
#Plotaxesm()geoshow('cn_province', edgecolor='gray')geoshow('country')levs = [10,15,20,25,30,35,40,45,50,55,60,65,70]imshow(lon, lat, data, levs, cmap='radar_2', vmin=0, vmax=75)colorbar(shrink=0.8)title('CREF - {}'.format(br.radartime))xlim(70, 140)ylim(15, 55)
#Add south China Seaaxesm(position=[0.76,0.18,0.15,0.2], facecolor='w', axison=False)geoshow('cn_border')xlim(106, 123)ylim(2, 23)


气python风雨
主要发一些涉及大气科学的Python文章与个人学习备忘录
 最新文章