GrADS格点数据文件解析

目录

本文介绍 GrADS 格点数据的描述文件的解析和二进制数据文件的读取。

关于 GrADS 格点数据格式的详细介绍,请浏览《GrADS格点数据格式》。

数据描述文件解析

从数据描述文件 (简称 ctl 文件) 格式来看,每个独立的 ctl 语句行都以一个关键字开头。 有些关键字只有一行语句,比如 dsettitle 等;有些关键字可能有多行语句,比如 zdefvars 等,后续语句行数都可以从第一行语句中得到。

所以解析 ctl 文件是顺序读取每行,第一个单词为关键字,根据关键字采取相应的操作,比如 dset 只需读当前行,而 vars 需要读取后面所有的变量记录加上 endvars 关键字,返回最后读取的行数。 接下来继续从下一行开始解析文件。

框架图后续补充。

首先将 ctl 文件内容保存为字符串数组

self.ctl_file_path = Path(ctl_file_path)
with open(ctl_file_path) as f:
    lines = f.readlines()
    self.ctl_file_lines = [l.strip() for l in lines]
    self.cur_no = 0

按顺序解析,读取一行,得到该行对应的解析器,运行解析器,解析器可能会读取更多的行,再从新一行继续解析

total_lines = len(self.ctl_file_lines)
while self.cur_no < total_lines:
    cur_line = self.ctl_file_lines[self.cur_no]
    first_word = cur_line[0:cur_line.find(' ')]
    if first_word.lower() in self.parser_mapper:
        self.parser_mapper[first_word]()
    self.cur_no += 1

每个关键字对应一个解析器:

self.parser_mapper = {
    'ctl_file_name': self._parse_ctl_file_name,
    'dset': self._parse_dset,
    'options': self._parse_options,
    'title': self._parse_title,
    'undef': self._parse_undef,
    'xdef': self._parse_dimension,
    'ydef': self._parse_dimension,
    'zdef': self._parse_dimension,
    'tdef': self._parse_tdef,
    'vars': self._parse_vars,
}

下面介绍一些解析器

dset

替换 ^ 为绝对路径,例如

dset ^postvar2021080200_024

判断路径是否为模板,例如

dset ^postvar2021092700%f3%n2

options

查看是否设置 big_endianlittle_endianyrev 等参数。

dimension

适用于 xdefydef,根据映射类型,采用不同的方式设置层次。

对于 linear 类型

xdef 1440 linear    0.0000    0.2500

使用层数、起始值和步进值生成层次列表

levels = [start + step * n for n in range(count)]

对于 levels 类型

zdef   27 levels
    1000.000
    925.0000
    850.0000
    700.0000
    ...

读取后续层次值组成列表。

tdef

对于起始时间,目前只考虑业务系统中没有分钟的形式。

start_date = pd.to_datetime(start_string.lower(), format='%Hz%d%b%Y')

对于时间增量 vvkk,使用 pd.Timedelta 生成时间增量对象 time_step,目前仅支持分钟、小时和天 (即 kkmnhrdy)。

最后生成时间列表:

values = [start_date + time_step * i for i in range(count)]

vars

目前只支持 v2.0.1 及之前版本的格式,不考虑附加码。 读取每条记录,分析层次类型 (是否使用 zdef 设置的层次),生成变量列表 self.grads_ctl.vars

再根据变量列表,加上相应的层次信息,生成数据记录列表 self.grads_ctl.record,对应数据文件中的每条记录。

二进制文件读取

从格式说明中可以知道,二进制文件分为两种:

  • 带 fortran 长度记录
  • 不带 fortran 长度记录

通过 ctl 文件的 options 中是否设置 sequential 判断。

Fortran 长度记录为 4 字节,首尾各一个。 每个数据都是 4 字节的浮点数。

读取数据时首先要找到记录的开头。 因为 ctl 文件中每个记录的网格相同,所以每条记录占用的空间也相同,所以只需通过记录的索引号即可得到记录的偏移。

带 Fortran 长度记录:

offset = (nx * ny + 2) * 4 * record_index

不带 Fortran 长度记录:

offset = nx * ny * 4 * record_index

之后就是 nx*ny 个四字节 float 型数据,先沿 x 轴存放,再沿 y 轴存放。

数据的解码与字节顺序有关,通过 ctl 文件的 options 中是否设置 big_endianlittle_endian 判断。

设置文件读取模式,大端使用 >f,小端使用 <f,默认与本地相同 (f):

if self.grads_ctl.data_endian == 'big':
    data_format = '>f'
elif self.grads_ctl.data_endian == 'little':
    data_format = '<f'
else:
    # print("Data endian is not found. Use local endian to unpack values.")
    data_format = 'f'

使用 numpy 的 np.fromrile() 函数从数据文件的 offset 偏移处读取一维数组,并重组为二维数组:

data_file.seek(offset)

values = np.fromfile(
    data_file, 
    dtype=np.dtype(data_format), 
    count=x_count * y_count
)
values = values.reshape((y_count, x_count))

参考

详细解析程序可以参见我写的一个小程序:

perillaroc/porter

上述解析程序已集成到 nwpc-oper/nwpc-data 项目中。