ecCodes学习笔记:GRIB2文件插值与裁剪

目录

使用 ecCodes 和 scipy 实现对 GRIB2 文件的裁剪与插值

ecCodes 提供 Python API,可以方便地解码 GRIB2 数据。
GRIB2 文件由多个消息构成,循环使用 codes_grib_new_from_file() 方法,逐个读取 GRIB2 消息。

with open(file_path, 'rb') as f:
    while 1:
        grib_message = eccodes.codes_grib_new_from_file(f)
        if grib_message is None:
            break

ecCodes 内部使用 NumPy 保存数据,支持 SciPy 技术栈。 scipy 的 interpolation 包提供多种插值方法,支持从一维到n维的各类数据。 GRIB2 数据可以看做二维数据,因此可以使用 scipy 实现裁剪与插值。

获取源数据信息

使用 ecCodes 获取 GRIB 数据的网格信息

left_lon = eccodes.codes_get(grib_message, 'longitudeOfFirstGridPointInDegrees')
right_lon = eccodes.codes_get(grib_message, 'longitudeOfLastGridPointInDegrees')
lon_step = eccodes.codes_get(grib_message, 'iDirectionIncrementInDegrees')
nx = eccodes.codes_get(grib_message, 'Ni')

top_lat = eccodes.codes_get(grib_message, 'latitudeOfFirstGridPointInDegrees')
bottom_lat = eccodes.codes_get(grib_message, 'latitudeOfLastGridPointInDegrees')
lat_step = eccodes.codes_get(grib_message, 'jDirectionIncrementInDegrees')
ny = eccodes.codes_get(grib_message, 'Nj')

orig_values = eccodes.codes_get_values(grib_message)

GRIB2 数据点保存顺序由 scanning mode 值决定。 NWPC 生成的 GRIB2 数据 scanning mode 都是 0,也就是经度从小到大,纬度从大到小,同纬度的点排在一起(按行排列)。 下面只考虑该类型的数据。

gribdata 实现

scipy.interpolate.griddata(
    points, 
    values, 
    xi, 
    method='linear', 
    fill_value=nan,
    rescale=False
)

gribdata() 方法支持对任意纬度数据的插值,用法比较简单。
首先要准备插值需要的源数据点坐标和目标数据点坐标。

points

源数据每个点的坐标数据,按纬度分开,每个维度一个数组。

# TODO: meshgrid()...
lons = []
lats = []
for y in np.arange(top_lat, bottom_lat, -lat_step):
    for x in np.arange(left_lon, right_lon, lon_step):
        lons.append(x)
        lats.append(y)

xi

目标数据每个点的坐标,每个坐标对应插值后的一个点。 注意坐标范围要在源数据的范围内,超出范围的数据会使用 fill_value 填充。

points = []
for y in np.arange(top_lat, bottom_lat, -lat_step):
    for x in np.arange(left_lon, right_lon, lon_step):
       points.append([x, y])

下面调用 gribdata(),就可以返回插值和裁剪后的数据数组。

target_values = griddata(
  (orig_lons, orig_lats),
  orig_values, 
  target_points, 
  method='linear'
)

修改数据范围需要修改上一步提取的与数据范围相关的 key,并将数据更新为插值后的数据。

eccodes.codes_set(grib_message, 'longitudeOfFirstGridPointInDegrees', left_lon)
eccodes.codes_set(grib_message, 'longitudeOfLastGridPointInDegrees', right_lon)
eccodes.codes_set(grib_message, 'iDirectionIncrementInDegrees', lon_step)
eccodes.codes_set(grib_message, 'Ni', nx)

eccodes.codes_set(grib_message, 'latitudeOfFirstGridPointInDegrees', top_lat)
eccodes.codes_set(grib_message, 'latitudeOfLastGridPointInDegrees', bottom_lat)
eccodes.codes_set(grib_message, 'jDirectionIncrementInDegrees', lat_step)
eccodes.codes_set(grib_message, 'Nj', ny)

eccodes.codes_set_values(grib_message, target_values)
eccodes.codes_write(grib_message, output_file)

对 regular_ll 网格类型的优化

gribdata 针对任意的n维数据进行插值,所以效率不高。 GRIB2 数据通常都是二维网格数据,scipy 提供针对这类数据的插值类 RegularGridInterpolator

class scipy.interpolate.RegularGridInterpolator(
    points, 
    values, 
    method='linear', 
    bounds_error=True, 
    fill_value=nan
)

这里的 points 和上面略有不同

points

每个纬度的坐标值:

y_array = np.arange(bottom_lat, top_lat, lat_step)
x_array = np.arange(left_lon, right_lon, lon_step)

values

原始 values 为一维数组,需要 reshape 为二维矩阵。
下面构建插值类,调用生成的对象得到插值后的结果。

target_function = RegularGridInterpolator(
    (orig_y_array, orig_x_array), 
    orig_values.reshape(ny, nx)
)

points = []
for y in np.arange(bottom_lat, top_lat, lat_step):
    for x in np.arange(left_lon, right_lon, lon_step):
        points.append([y, x])

target_values = target_function(target_xy_points)