利用python解码GDAS的prepbufr格式数据并画出数据来源站点分布

当我们使用GSI同化系统来对大气数据进行同化时,都要使用bufr/prepbufr格式的数据,本文以解码GDAS的prepbufr格式数据来可视化所用的数据信息。prepbufr数据的解码编译一般是使用的Fortran语言,但python也有相应的包可以使用
可以参考github库ncepbufr

GDAS以及prepbufr数据简介

  1. GDAS介绍
    GDAS(Global Data Assimilation System,全球资料同化系统)GDAS是由美国国家环境预报中心(NCEP)开发和维护的一个全球数值天气预报系统。GDAS的主要功能是将全球观测数据(如卫星、雷达、地面站、气球探测等)与数值天气预报模型结合,通过同化技术生成一个最优的初始条件,这些初始条件将用于驱动数值天气预报模型,以提高预报的准确性。
    GDAS通常会以6小时为一个周期进行同化和更新,生成全球范围内的分析场,这些分析场可以用来初始化数值天气预报模型。GDAS的输出产品不仅为NCEP的全球预报系统(GFS)提供初始条件,还为其他气象研究和应用提供了高质量的初始数据。质量控制后的数据
    2.prepbufr 数据介绍
    PreBUFR格式的数据是一种用于存储气象观测数据的临时二进制格式。它采用高效的方式存储各种类型的观测数据,如卫星、雷达和地面观测数据。这种格式在存储时会将数据压缩,以减少文件大小,同时保留数据的精度和完整性。PreBUFR文件的设计目标是为了在数据同化和预报系统中快速读取和处理数据。在处理完成后,PreBUFR数据通常会被转换为标准的BUFR格式。
    prepbufr数据使用指南(英文)

prebufr结构

我在阅读很多关于prepbufr的文件后发现其答题结构如下
prebufr数据以单个事件储存,每个有四类词条(每个不一定全部有对应的所有描述),其中hdstr是头文件相当于对于一个事情的定义(时间地点(经纬、高度)观测类型),SAID、T29可以没有,但前面个的站点名称、经度、纬度、时间间距(距离所同化时间的间距h,时间在GDAS数据名字里有)、prepbufr类型、海拔必须全面。

# BUFR 文件配置
hdstr='SID XOB YOB DHR TYP ELV SAID T29'                        #{站点名 经度 纬度 时间间距(h) prebufr类型 海拔(m) 卫星标识 输入类型}
obstr='POB QOB TOB ZOB UOB VOB PWO MXGS HOVI CAT PRSS TDO PMO'  #观测{压强 比湿 温度 高度 U V 总降水 未知 未知 数据类型 表面气压 露点温度 平均海平面压力}
qcstr='PQM QQM TQM ZQM WQM NUL PWQ PMQ'                         #质量{压强 比湿 温度 高度 风 未知 总降水 平均海平面压力}
oestr='POE QOE TOE NUL WOE NUL PWE'                             #误差{压强 比湿 温度 未知 风 未知 降水}

站点可视化

将prebufr数据主要信息导入新的text文件

程序如下

from __future__ import print_function
import ncepbufr #github里有下载教程,链接在上方
import sys

def save_print_output_to_file(file_path, *args, **kwargs):
    with open(file_path, 'a') as f:  # 使用 'a' 模式以追加模式打开文件
        sys.stdout = f  # 重定向标准输出流到文件
        print(*args, **kwargs)
        sys.stdout = sys.__stdout__  # 恢复标准输出流
# 假设 hdstr、obstr、qcstr、oestr 是你的字符串
# 假设 file_path 是你希望保存的文件路径
file_path = '/bufr_work/prebufr_data/prebufr_output.txt'#保存生成的text文件地址

# 在循环外部打开文件
with open(file_path, 'w') as f:
    pass  # 确保文件是空的,或者你可以选择清除文件内容
6

hdstr='SID XOB YOB DHR TYP ELV SAID T29'
obstr='POB QOB TOB ZOB UOB VOB PWO MXGS HOVI CAT PRSS TDO PMO'
qcstr='PQM QQM TQM ZQM WQM NUL PWQ PMQ'
oestr='POE QOE TOE NUL WOE NUL PWE'

# read prepbufr file.

bufr = ncepbufr.open("/prebufr_diy/2019080112.prebufr.nr")#需要解码的prepbufr数据地址(我这里是自己编译的prebufr不是GDAS的)
bufr.print_table() # print embedded table
bufr.dump_table("/obs/gdas.t12z.prepbufr.table") # dump table to file(解码需要的辅助文件)
while bufr.advance() == 0: # loop over messages.
    print(bufr.msg_counter, bufr.msg_type, bufr.msg_date, bufr.receipt_time)
    #bufr.read_subset(obstr) # should raise subset not loaded error
    while bufr.load_subset() == 0: # loop over subsets in message.
        hdr = bufr.read_subset(hdstr).squeeze()
        station_id = hdr[0].tostring()
        obs = bufr.read_subset(obstr)
        nlevs = obs.shape[-1]
        oer = bufr.read_subset(oestr)
        qcf = bufr.read_subset(qcstr)
        #print('station_id, lon, lat, time, station_type, levels =',\
        #station_id,hdr[1],hdr[2],hdr[3],int(hdr[4]),nlevs)
        # 假设 file_path 是你希望保存的文件路径
        # 将 print 输出保存到文件中
        save_print_output_to_file(file_path,'station_id,lon,lat, time,prebufr_type,input_type =',station_id,hdr[1],hdr[2],hdr[3],hdr[4],hdr[-1])

    # stop after first 2 messages.
    #if bufr.msg_counter == 2: break
bufr.close()
print('大王,数据处理完毕*—*')

值得注意的是其中需要的prepbufr.table文件,它相当于你翻译英文时用到的字典,将对应的二进制数据翻译成text在github的ncepbufr库里prepbufr.table可以下载到,prebufr官网也能下载到。

站点可视化(经纬度分布,类型区分)

在TYP中,以不同的数字对数据类型进行分类一般大于200是风资料,小于的是质量点信息即温压湿等标量资料prepbufr type table
代码如下

import codecs
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cfeature
from matplotlib.lines import Line2D

# 文件路径
filepath = "/bufr_work/prebufr_data/prebufr_output.txt"
save_path = "/bufr_work/2019080112global_prebufr_sit.png"#图片保存地址

# 读取数据
lon, lat, prebufr_type, input_type = [], [], [], []
with codecs.open(filepath, mode='r', encoding='utf-8') as f:
    for line in f:
        a = line.split()
        if len(a) >= 6:
            try:
                b = np.float32(a[5])  # 使用 float32 提高精度
                c = np.float32(a[6])
                d = np.float32(a[8])  # 转换为浮点数
                lon.append(b)
                lat.append(c)
                prebufr_type.append(d)
                #input_type.append(e)
            except ValueError as e:
                print(f"数据转换错误: {e},行内容: {line}")
        else:
            print(f"行数据不完整,已跳过: {line}")

# 计算 prebufr_type 的唯一值数量
unique_prebufr_types = set(prebufr_type)
num_unique_prebufr_types = len(unique_prebufr_types)

# 打印唯一值和总数量
print(f"Unique prebufr types: {unique_prebufr_types}")
print(f"Number of unique prebufr types: {num_unique_prebufr_types}")
print(f"Total number of prebufr types: {len(prebufr_type)}")

# 定义颜色和标签字典
colors = {
    'SATWND & ASCATW': 'blue',
    'ADPSFC': 'red',
    'SFCSHP': 'green',
    'ADPUPA': 'orange',
    'AIRCFT': 'purple',
    'VADWND & RASSDA': 'brown'
}

categories = {
    'SATWND & ASCATW': [242, 243, 250, 252, 253, 254, 290],
    'ADPSFC': [181, 187, 281, 287, 284, 183],
    'SFCSHP': [180, 280, 282],
    'ADPUPA': [120, 220, 221],
    'AIRCFT': [130, 230],
    'VADWND & RASSDA': [126, 224]
}

# 计算每个类别的点数量
category_counts = {}
for category, prebufr_types in categories.items():
    count = sum(1 for t in prebufr_type if t in prebufr_types)
    category_counts[category] = count

# 根据点数量对类别进行排序
sorted_categories = sorted(category_counts.items(), key=lambda item: item[1], reverse=True)

fig = plt.figure(figsize=(16, 12), dpi=800)
ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())

# 添加地图特征
ax.add_feature(cfeature.LAND, facecolor='lightgray')
ax.add_feature(cfeature.COASTLINE)
ax.add_feature(cfeature.BORDERS, linestyle=':')

# 绘制实际散点图
for category, count in sorted_categories:
    prebufr_types = categories[category]
    lons = [lon[i] for i in range(len(lon)) if prebufr_type[i] in prebufr_types]
    lats = [lat[i] for i in range(len(lat)) if prebufr_type[i] in prebufr_types]
    size = 2 if category in ['SATWND & ASCATW', 'ADPSFC'] else 10  # 根据类别设置实际大小
    color = colors[category]
    facecolor = 'none' if category == 'SATWND & ASCATW' else color  # 空心圆点
    edgecolor = 'blue' if category == 'SATWND & ASCATW' else color  # 设置边缘颜色
    ax.scatter(lons, lats, marker='o', facecolors=facecolor, edgecolors=edgecolor, linewidths=0.5, s=size, transform=ccrs.PlateCarree())

# 创建图例句柄和标签
legend_handles = []
legend_labels = []
for category, color in colors.items():
    if category == 'SATWND & ASCATW':
        handle = Line2D([0], [0], marker='o', color='w', markerfacecolor='none', markeredgecolor='blue', markeredgewidth=1, markersize=12, linestyle='None')
    else:
        handle = Line2D([0], [0], marker='o', color='w', markerfacecolor=color, markersize=12, linestyle='None')
    legend_handles.append(handle)
    label = f"{category} ({category_counts[category]})"
    legend_labels.append(label)

# 创建图例
ax.legend(handles=legend_handles, labels=legend_labels, title='PreBUFR Type Categories', loc='lower left')

# 设置地图范围
ax.set_extent([0, 359, -89, 89], crs=ccrs.PlateCarree())

# 添加网格线
ax.gridlines(draw_labels=True)

# 添加标题和标签
plt.title('Observation Sites(2024_06_28_12)')
plt.xlabel('Longitude')
plt.ylabel('Latitude')

# 调整布局
plt.tight_layout()

# 保存图像
plt.savefig(save_path, format='png', dpi=800)

print('图已经画好了')

需要注意的地方是,我先打印出我所使用的prepbufr数据所有类型,才根据打印出的类型来设立的分类category,实际的分类当然不止这些,想作全面的可以参考prebufr type的详细分类prepbufr type table,然后由于卫星的风数据站点太多,我将对应的格点做了相应的调整来美化分布图
观测站点全球分布图
你也可只画你同化范围的站点分布
nearby
不颜色的类型分类在官网中都有解释有地面站、卫星风、雷达、飞机等。
附带各种风类分开画的程序

import codecs
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import os

# 文件路径
filepath = "/obs/prebufr_output.txt"
save_folder = "/bufr_work/prebufr site sort"

# 确保保存文件夹存在
os.makedirs(save_folder, exist_ok=True)

# 读取数据
lon, lat, prebufr_type = [], [], []
with codecs.open(filepath, mode='r', encoding='utf-8') as f:
    for line in f:
        a = line.split()
        if len(a) >= 6:
            try:
                b = np.float32(a[2])
                c = np.float32(a[3])
                d = np.float32(a[4])
                lon.append(b)
                lat.append(c)
                prebufr_type.append(d)
            except ValueError as e:
                print(f"数据转换错误: {e},行内容: {line}")
        else:
            print(f"行数据不完整,已跳过: {line}")

# 定义分类及其颜色
categories = {
    'SATWND & ASCATW': [242, 243, 250, 252, 253, 254, 290],
    'ADPSFC': [181, 187, 281, 287, 284, 183],
    'SFCSHP': [180, 280, 282],
    'ADPUPA': [120, 220, 221],
    'AIRCFT': [130, 230],
    'VADWND & RASSDA': [126, 224]
}

colors = {
    'SATWND & ASCATW': 'blue',
    'ADPSFC': 'red',
    'SFCSHP': 'green',
    'ADPUPA': 'orange',
    'AIRCFT': 'purple',
    'VADWND & RASSDA': 'brown'
}

# 为每个分类绘制图像
for category, prebufr_types in categories.items():
    lons = [lon[i] for i in range(len(lon)) if prebufr_type[i] in prebufr_types]
    lats = [lat[i] for i in range(len(lat)) if prebufr_type[i] in prebufr_types]

    fig = plt.figure(figsize=(10, 6), dpi=800)
    ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())

    # 添加地图特征
    ax.add_feature(cfeature.LAND, facecolor='lightgray')
    ax.add_feature(cfeature.COASTLINE)
    ax.add_feature(cfeature.BORDERS, linestyle=':')

    # 设置散点大小
    size = 1 if category in ['SATWND & ASCATW', 'ADPSFC'] else 3

    # 绘制散点,点大小设置为根据分类调整
    ax.scatter(lons, lats, color=colors[category], marker='o', s=size, transform=ccrs.PlateCarree())

    # 计算当前类别的点数量并构建图例标签
    count = len(lons)
    legend_label = f'{category} ({count})'

    # 创建图例句柄
    ax.legend([plt.Line2D([0], [0], marker='o', color='w', markerfacecolor=colors[category], markersize=10)], 
              [legend_label], loc='lower left')

    # 设置地图范围
    ax.set_extent([0, 359, -89, 89], crs=ccrs.PlateCarree())

    # 添加网格线
    ax.gridlines(draw_labels=True)

    # 添加标题和标签
    plt.title(f'Observation Sites - {category}')
    plt.xlabel('Longitude')
    plt.ylabel('Latitude')

    # 保存图像
    save_path = os.path.join(save_folder, f'{category.replace(" ", "_")}.png')
    plt.savefig(save_path, format='png', dpi=800)
    plt.close()  # 关闭图形,以释放内存

    print(f'图像已保存: {save_path}')

放两张效果图
地面站
船只走航

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿翔_ocean

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值