2. 观测数据文件格式解析
海洋气象观测数据从传感器到业务系统,往往要跨越多种文件格式。浮标CTD剖面常以NetCDF存储多维数组,船舶报和自动站分钟数据多以CSV交换,气象卫星与探空产品大量使用BUFR,数值预报场和再分析资料则以GRIB为主。能否准确解析这些格式,直接决定了后续质控能否拿到正确的变量、单位、时间戳和空间坐标。本节围绕“结构—读取—元数据提取”三个环节,逐一拆解四类常见格式,并给出可复用的读取代码与元数据核对方法。
一、四类格式的结构差异
NetCDF是一种自描述的多维数组格式,内部由维度(dimension)、变量(variable)和全局属性(global attribute)构成。一个典型的浮标剖面文件会定义 time、depth、lat、lon 四个维度,变量 TEMP 的维度顺序为 (time, depth),并通过 _FillValue、scale_factor、add_offset 属性描述缺测值与打包压缩规则。读取时必须先解析这些属性,否则会把整型存储的原始值误当作物理量。
CSV是纯文本表格,结构信息几乎全部依赖表头和约定。海洋气象CSV常见的问题是表头命名不统一(temp、WTMP、水温并存)、时间列格式混杂(ISO 8601、YYYYMMDDHH、Unix时间戳)、缺测用 -999、9999 或空串表示。读取时不能只靠 pandas.read_csv 的默认推断,必须显式指定 dtype、na_values 和 parse_dates。
BUFR是二进制表格驱动格式,其结构由“段”组成:指示段给出总长度和编辑版本,标识段携带观测类型、站点、时间等元数据,数据段则按“描述符”序列编码。描述符分三类:F(表B,要素描述符,如011001表示气温)、X(表C,操作符,如改变宽度或复用)、Y(表D,本地序列,如310002表示某类探空序列)。要读懂BUFR,必须同时持有WMO的表B、表C、表D,否则只能看到一串数字。
GRIB同样是二进制自描述格式,GRIB2由第0段(指示段)、第1段(标识段)、第2段(本地使用段)、第3段(网格定义段)、第4段(产品定义段)、第5段(数据表示段)、第6段(位图段)和第7段(数据段)组成。网格类型、投影参数、层次类型、参数类别与编号、打包方式都写在对应段中。GRIB1结构更简单但参数表较老,业务中仍会遇到。
二、读取方法与关键参数
NetCDF推荐使用 xarray 打开,它能自动解析维度坐标和属性,并支持惰性加载。读取时务必检查 ds.variables 的 units 与 _FillValue,对打包变量调用 mask_and_scale=True(默认开启)。若变量维度顺序与预期不符,用 transpose 显式调整,避免后续按轴索引时错位。
import xarray as xr
ds = xr.open_dataset("buoy_20240601.nc", mask_and_scale=True)
print(ds["TEMP"].attrs) # 查看 units / _FillValue / scale_factor
temp = ds["TEMP"].transpose("time", "depth")
temp_c = temp.where(temp != temp.attrs.get("_FillValue", -9999))
CSV读取的核心是把“隐式约定”显式化。下面示例把常见缺测标记统一为 NaN,并强制时间列按UTC解析:
import pandas as pd
df = pd.read_csv("ship_obs.csv",
na_values=["-999", "9999", ""],
parse_dates=["obs_time"],
dtype={"station_id": str, "temp": float})
df["obs_time"] = pd.to_datetime(df["obs_time"], utc=True)
BUFR与GRIB建议统一走ECMWF的ecCodes体系。命令行用 bufr_dump -p 查看BUFR的描述符展开结果,用 grib_ls -p shortName,units,level,dataDate,dataTime 快速列出GRIB字段;Python侧用 eccodes 或 cfgrib 读取。GRIB转NetCDF可用 cfgrib 的 xr.open_dataset(engine="cfgrib"),但要注意同一文件含多个层次或时效时需加 backend_kwargs={"filter_by_keys": {...}} 过滤,否则会因超集冲突报错。
三、元数据提取实战
元数据提取不是“打印一下属性”,而是把分散在各格式中的关键信息归一到同一张清单,供质控规则消费。以一次浮标温度数据接入为例,需要提取并核对以下字段:变量标准名、原始单位、时间基准与覆盖范围、经纬度、深度层次、缺测标记、数据打包方式、文件版本与生成时间。
实际操作中,先写一个格式无关的提取函数,返回统一字典,再逐格式填充。例如NetCDF从 ds.attrs 和变量属性取,CSV从表头映射表和首末行取,BUFR从标识段的 dataCategory、typicalDate、typicalLatLon 取,GRIB从第1段和第3、4段取。提取完成后做三项一致性检查:单位是否为开尔文或摄氏度并明确标注、时间是否全部为UTC、经纬度是否落在合理海域范围。任何一项不通过,都应回到读取参数修正,而不是带着错误元数据进入质控。
scale_factor 与 add_offset 未应用,导致温度出现整型量级;CSV把 9999 当有效值参与统计;GRIB未按 filter_by_keys 过滤,读到错误的层次或时效。这三类错误在质控前必须排除。完成元数据提取后,建议把结果落成一份可核对的清单文件(如JSON或CSV),记录每个变量的来源、单位、时间范围与缺测数量。这样在后续质控中一旦发现异常,能快速判断是观测本身的问题,还是读取环节把单位、时间或坐标弄错了。格式解析的终点不是“能读出来”,而是“读出来的每个字段都可追溯、可核对、可直接进入质控流程”。