使用 with 语句关闭 GDAL 数据集

Use with statement to close GDAL datasets

考虑使用来自 Jeff Knupp's blogwith 语句的这个基本示例:

class File():

    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode

    def __enter__(self):
        self.open_file = open(self.filename, self.mode)
        return self.open_file

    def __exit__(self, *args):
        self.open_file.close()

我有一个 testfile,其中包含两行,'ABC' 和 'DEF'。一切都按预期工作:

with File('/tmp/testfile','r') as f:
    txt = [x.strip() for x in f.readlines()]
    print(txt)

# ['ABC', 'DEF']

with 块外调用 class 方法给出了预期的错误:

f.readlines()

ValueError Traceback (most recent call last) in () ----> 1 f.readlines()

ValueError: I/O operation on closed file.


现在回答我的问题:

如何使用 gdal 对象而不是文件实现相同的行为?

我有一个 class 方法,它应该从磁盘读取数据并将其放入 gdal 栅格中以供进一步处理。完成后,我想适当地关闭生成的 gdal 栅格。通常这是通过将其设置为 None 并使用 gdal.Unlink.

来完成的

但是,当我像前面的示例一样将所有内容都放入上下文结构中时,我仍然可以与 with 块之外的数据集进行交互。

这是一个可重现的例子:

class Raster:
    '''Raster class with sidelength s'''
    def __init__(self,s):
        self.sidelength = s

    def __enter__(self):
        # create raster in memory
        driver = gdal.GetDriverByName('GTiff')
        self.raster = driver.Create('/vsimem/inmem.tif', self.sidelength, self.sidelength, 1, gdal.GDT_Float32)
        self.raster.GetRasterBand(1).WriteArray(np.random.rand(self.sidelength,self.sidelength))
        return self.raster

    def __exit__(self, *args):
        # close file and unlink
        self.raster = None
        gdal.Unlink('/vsimem/inmem.tif')

with 块按预期工作:

with Raster(5) as r:
    print(r)

# <osgeo.gdal.Dataset; proxy of <Swig Object of type 'GDALDatasetShadow *' at 0x7f8078862ae0> >

但是在块之后对象仍然存在,我仍然可以读取值:

print(r)

#<osgeo.gdal.Dataset; proxy of <Swig Object of type 'GDALDatasetShadow *' at 0x7f8078044a20> >

print(r.ReadAsArray())

#[[0.2549882  0.80292517 0.23358545 0.6284887  0.7294142 ]
# [0.9310723  0.21535267 0.9054575  0.60967094 0.9937953 ]
# [0.69144976 0.01727938 0.16800325 0.61249655 0.1785022 ]
# [0.16179436 0.43245795 0.7042811  0.4809799  0.85534436]
# [0.67751276 0.7560658  0.9594516  0.6294476  0.3539126 ]]

正如评论中所建议的那样,Rasterio 通过 rasterio.open 实现了您正在寻找的行为。

您不能在保留对 gdal 数据集的引用的同时真正关闭它。您可以保留对 Raster 实例的引用,并使用 r.raster 访问 with 块内的数据集。

class Raster:
    '''Raster class with sidelength s'''
    def __init__(self,s):
        self.sidelength = s

    def __enter__(self):
        # create raster in memory
        driver = gdal.GetDriverByName('GTiff')
        self.raster = driver.Create('/vsimem/inmem.tif', self.sidelength, self.sidelength, 1, gdal.GDT_Float32)
        self.raster.GetRasterBand(1).WriteArray(np.random.rand(self.sidelength,self.sidelength))
        return self

    def __exit__(self, *args):
        # close file and unlink
        self.raster = None
        gdal.Unlink('/vsimem/inmem.tif')


with Raster(5) as r:
    print(r.raster)

输出:

<osgeo.gdal.Dataset; proxy of <Swig Object of type 'GDALDatasetShadow *' at 0x04564728> >

with 块之外无法访问数据集:

r.raster is None

输出:

True

如果您将另一个变量绑定到 r.raster,则会再次出现问题,因此您可能希望将它与您需要的任何功能一起完全封装在 Raster 实例中。在这一点上,您或多或少会重新发明 Rasterio,但如果您的需求很简单,那可能比依赖它更好。