从包含在shapefile边界内的netcdf文件中提取数据

4

我有以下shapefilenetcdf文件

我想从netcdf文件中提取包含在shapefile边界内的数据。

您有任何建议如何实现此目标吗?

shapefile对应于SREX区域11 北欧(NEU),netcdf文件是CMIP6气候模型数据输出的示例(ua变量)。我的期望输出必须为netcdf格式。


更新

到目前为止,我尝试使用NCL和CDO创建netcdf掩码,并将此掩码应用于原始的netcdf数据集。以下是步骤(以及NCL脚本):

#################
## remove plev dimension from netcdf file
cdo --reduce_dim -copy nc_file.nc nc_file2.nc

## convert longitude to -180, 180
cdo sellonlatbox,-180,180,-90,90 nc_file2.nc nc_file3.nc

## create mask 
ncl create_nc_mask.ncl

## apply mask
cdo div nc_file3.nc shape1_mask.nc nc_file4.nc 
#################

输出结果几乎正确。请看下面的图片。但是,形状文件的南边界(SREX 11,NEU)没有被正确捕获。因此,我认为生成netcdf掩码的NCL脚本存在问题。

enter image description here


您是否专门寻找nco/cdo/ncl解决方案,或者其他语言(例如Python)也可以? - Bart
谢谢@Bart。欢迎使用任何语言 :) - aaaaa
多么慷慨的赏金。 - Iman Nia
使用GDAL解决方案非常简单。将您的NetCDF文件视为“栅格”(在GIS术语中)。您需要使用与Shapefile相同的CRS转换为GeoTIFF。这也是微不足道的。请参见:https://gis.stackexchange.com/questions/118236/clip-raster-by-shapefile-in-parts - Daniel R. Livingston
2个回答

3

我重新利用一些旧的脚本/代码,很快就为Python解决方案想出了这个。它基本上只是循环遍历所有网格点,并检查每个网格点是否在形状文件的多边形内或外。结果是变量mask(具有True/False的数组),可以用来遮掩您的NetCDF变量。

注意:这使用Numba(所有@jit行)加速代码,尽管在这种情况下并不真正需要。如果您没有Numba,可以将它们注释掉。

import matplotlib.pyplot as pl
import netCDF4 as nc4
import numpy as np
import fiona
from numba import jit

@jit(nopython=True, nogil=True)
def distance(x1, y1, x2, y2):
    """
    Calculate distance from (x1,y1) to (x2,y2)
    """
    return ((x1-x2)**2 + (y1-y2)**2)**0.5

@jit(nopython=True, nogil=True)
def point_is_on_line(x, y, x1, y1, x2, y2):
    """
    Check whether point (x,y) is on line (x1,y1) to (x2,y2)
    """

    d1 = distance(x,  y,  x1, y1)
    d2 = distance(x,  y,  x2, y2)
    d3 = distance(x1, y1, x2, y2)

    eps = 1e-12
    return np.abs((d1+d2)-d3) < eps

@jit(nopython=True, nogil=True)
def is_left(xp, yp, x0, y0, x1, y1):
    """
    Check whether point (xp,yp) is left of line segment ((x0,y0) to (x1,y1))
    returns:  >0 if left of line, 0 if on line, <0 if right of line
    """

    return (x1-x0) * (yp-y0) - (xp-x0) * (y1-y0)

@jit(nopython=True, nogil=True)
def is_inside(xp, yp, x_set, y_set, size):
    """
    Given location (xp,yp) and set of line segments (x_set, y_set), determine
    whether (xp,yp) is inside polygon.
    """

    # First simple check on bounds
    if (xp < x_set.min() or xp > x_set.max() or yp < y_set.min() or yp > y_set.max()):
        return False

    wn = 0
    for i in range(size-1):

        # Second check: see if point exactly on line segment:
        if point_is_on_line(xp, yp, x_set[i], y_set[i], x_set[i+1], y_set[i+1]):
            return False

        # Calculate winding number
        if (y_set[i] <= yp):
            if (y_set[i+1] > yp):
                if (is_left(xp, yp, x_set[i], y_set[i], x_set[i+1], y_set[i+1]) > 0):
                    wn += 1
        else:
            if (y_set[i+1] <= yp):
                if (is_left(xp, yp, x_set[i], y_set[i], x_set[i+1], y_set[i+1]) < 0):
                    wn -= 1

    if wn == 0:
        return False
    else:
        return True

@jit(nopython=True, nogil=True)
def calc_mask(mask, lon, lat, shp_lon, shp_lat):
    """
    Calculate mask of grid points which are inside `shp_lon, shp_lat`
    """

    for j in range(lat.size):    
        for i in range(lon.size):
            if is_inside(lon[i], lat[j], shp_lon, shp_lat, shp_lon.size):
                mask[j,i] = True


if __name__ == '__main__':

    # Selection of time and level:
    time = 0
    plev = 0

    # Read NetCDF variables, shifting the longitudes
    # from 0-360 to -180,180, like the shape file:
    nc = nc4.Dataset('nc_file.nc')
    nc_lon = nc.variables['lon'][:]-180.
    nc_lat = nc.variables['lat'][:]
    nc_ua  = nc.variables['ua'][time,plev,:,:]

    # Read shapefile and first feature
    fc = fiona.open("shape1.shp")
    feature = next(iter(fc))

    # Extract array of lat/lon coordinates:
    coords = feature['geometry']['coordinates'][0]
    shp_lon = np.array(coords)[:,0]
    shp_lat = np.array(coords)[:,1]

    # Calculate mask
    mask = np.zeros_like(nc_ua, dtype=bool)
    calc_mask(mask, nc_lon, nc_lat, shp_lon, shp_lat)

    # Mask the data array
    nc_ua_masked = np.ma.masked_where(~mask, nc_ua)

    # Plot!
    pl.figure(figsize=(8,4))
    pl.subplot(121)
    pl.pcolormesh(nc_lon, nc_lat, nc_ua, vmin=-40, vmax=105)
    pl.xlim(-20, 50)
    pl.ylim(40, 80)

    pl.subplot(122)
    pl.pcolormesh(nc_lon, nc_lat, nc_ua_masked, vmin=-40, vmax=105)
    pl.xlim(-20, 50)
    pl.ylim(40, 80)

    pl.tight_layout()

在这里输入图片描述

编辑

可以使用以下代码将掩码写入NetCDF:

nc_out = nc4.Dataset('mask.nc', 'w')
nc_out.createDimension('lon', nc_lon.size)
nc_out.createDimension('lat', nc_lat.size)

nc_mask_out = nc_out.createVariable('mask', 'i2', ('lat','lon'))
nc_lon_out = nc_out.createVariable('lon', 'f8', ('lon'))
nc_lat_out = nc_out.createVariable('lat', 'f8', ('lat'))

nc_mask_out[:,:] = mask[:,:]  # Or ~mask to reverse it
nc_lon_out[:] = nc_lon[:]     # With +180 if needed
nc_lat_out[:] = nc_lat[:]

nc_out.close()

谢谢。它运行正常。我如何将nc_ua_masked、nc_lat和nc_lon变量保存为唯一的netcdf文件? - aaaaa
此外...nc_ua_masked数组并不是完整的每日ua值数组..我没有解决我的问题。 - aaaaa
甚至将掩码保存为带有纬度、经度和TRUE/FALSE变量的netcdf文件也是不错的选择。 - aaaaa
我添加了一个如何将掩码写入NetCDF的示例。它使用“short”将掩码写为“0-1”,NetCDF4似乎不支持布尔值(?)。作为替代方案,也可以在所有级别和时间步骤上应用掩码(在NetCDF文件的副本上进行)。 - Bart

0

目前我想到了这个(我知道它不是完整的解决方案):

1) 要打开shapefile和nc文件,你需要安装两个包:

pip3 install pyshp
pip3 install netCDF4

2) 然后这是您在Python中导入它们的方式:

import shapefile
from netCDF4 import Dataset

3) 从 shapefile 中读取数据:

with shapefile.Reader("shape1.dbf") as dbf:
    print(f'{dbf}\n')
    print(f'bounding box: {dbf.bbox}')
    shapes = dbf.shapes()
    print(f'points: {shapes[0].points}')
    print(f'parts: {shapes[0].parts}')
    print(f'fields: {dbf.fields}')
    records = dbf.records()
    dct = records[0].as_dict()
    print(f'record: {dct}')

这将为您提供输出:
shapefile Reader
    1 shapes (type 'POLYGON')
    1 records (4 fields)

bounding box: [-10.0, 48.0, 40.0, 75.0]
points: [(-10.0, 48.0), (-10.0, 75.0), (40.0, 75.0), (40.0, 61.3), (-10.0, 48.0)]
parts: [0]
fields: [('DeletionFlag', 'C', 1, 0), ['NAME', 'C', 40, 0], ['LAB', 'C', 40, 0], ['USAGE', 'C', 40, 0]]
record: {'NAME': 'North Europe [NEU:11]', 'LAB': 'NEU', 'USAGE': 'land'}

4) 读取nc文件:

nc_fdata = Dataset('nc_file.nc', 'r')

5) 使用此辅助函数查看内部内容:

def ncdump(nc_fid, verb=True):
    def print_ncattr(key):
        try:
            print("\t\ttype:", repr(nc_fid.variables[key].dtype))
            for ncattr in nc_fid.variables[key].ncattrs():
                print('\t\t%s:' % ncattr, repr(nc_fid.variables[key].getncattr(ncattr)))
        except KeyError:
            print("\t\tWARNING: %s does not contain variable attributes" % key)

    # NetCDF global attributes
    nc_attrs = nc_fid.ncattrs()
    if verb:
        print("NetCDF Global Attributes:")
        for nc_attr in nc_attrs:
            print('\t%s:' % nc_attr, repr(nc_fid.getncattr(nc_attr)))
    nc_dims = [dim for dim in nc_fid.dimensions]  # list of nc dimensions
    # Dimension shape information.
    if verb:
        print("NetCDF dimension information:")
        for dim in nc_dims:
            print("\tName:", dim)
            print("\t\tsize:", len(nc_fid.dimensions[dim]))
            print_ncattr(dim)
    # Variable information.
    nc_vars = [var for var in nc_fid.variables]  # list of nc variables
    if verb:
        print("NetCDF variable information:")
        for var in nc_vars:
            if var not in nc_dims:
                print('\tName:', var)
                print("\t\tdimensions:", nc_fid.variables[var].dimensions)
                print("\t\tsize:", nc_fid.variables[var].size)
                print_ncattr(var)
    return nc_attrs, nc_dims, nc_vars

nc_attrs, nc_dims, nc_vars = ncdump(nc_fdata)

我猜你会需要叫做 'ua' 的变量,因为它包含了经度和纬度地址等信息。
所以为了构建遮罩层,你需要从 'ua' 中提取那些经纬度在形状文件边界框值之间的所有内容。

这怎么回答问题呢?读取NetCDF和shape文件是问题中微不足道的部分(几乎每个在线教程都覆盖了这个部分)。 - Bart
在第一行中,我写道这不是一个完整的解决方案。用户想从文件中提取数据,我帮助他用Python如何做到这一点,并留下了提示他如何继续。 - kalzso
我的观点是:你把最困难的部分留给了别人(只“解决”了琐碎的部分),在这种情况下,我不认为这是一个答案。 - Bart
1
它并不是为了解决问题而存在的,我只是想给用户一个起点。另外,这太长了,无法放在注释中,所以我不得不写成“答案”。 - kalzso

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接