GIS遥感数据工程【免费下载链接】gdalGDAL is an open source MIT licensed translator library for raster and vector geospatial data formats.项目地址https://gitcode.com/gh_mirrors/gd/gdal点击查看免费下载导读GDAL 与 OGR 的 Python 绑定osgeo.gdal、osgeo.ogr等模块是地理空间数据处理中最常用的接口之一但其行为与原生 Python 惯例存在若干显著差异默认不抛异常、对象生命周期由底层 C 对象的所有权关系决定、部分绑定特有的崩溃与行为需要专门的规避手段。本文以 python_gotchas.rst 为骨架结合本仓库的 SWIG 绑定源码swig/include/python/python_exceptions.i、swig/include/ogr.i与 OGR 核心实现ogr/ogrsf_frmts/generic/ogrlayer.cpp逐条剖析这些陷阱的成因、复现方式与正确写法帮助读者在编写 GDAL/OGR Python 代码时避开崩溃、数据未落盘与隐蔽的逻辑错误。一、总览为什么 Python 绑定会不按 Python 惯例行事GDAL/OGR 的 Python 绑定通过 SWIG 将 C 库封装为 Python 模块。绑定层的行为受两条底层事实制约错误处理模型来自 C 库C 层通过CPLErr错误码与错误处理器error handler机制上报错误绑定层再决定是否将其翻译成 Python 异常对象所有权ownership关系来自 C 对象图GDALDataset拥有GDALRasterBandOGRFeature拥有OGRGeometryPython 对象只是这些 C 对象的影子SWIG 中的*Shadow类型即由此而来。因此下列Gotchas多数并非 Bug而是绑定层在保持向后兼容的前提下自然形成的行为——文档原文也明确指出Not all items listed here are bugs. Some of these are just how GDAL and OGR work and cannot be fixed easily without breaking existing code.并非所有条目都是 Bug有些只是 GDAL/OGR 的工作方式不经破坏既有代码就难以修复。下文按设计使然 / 历史遗留与外部软件行为导致两大类展开。二、默认不抛异常必须显式调用UseExceptions()2.1 默认行为返回错误值 向sys.stdout打印错误默认情况下GDAL 与 OGR 的 Python 绑定在出错时不会抛出异常而是返回一个错误值通常是None并把错误消息打印到sys.stdout。例如尝试打开一个不存在的数据集 from osgeo import gdal gdal.Open(C:\\foo.img) ERROR 4: C:\foo.img does not exist in the file system, and is not recognized as a supported dataset name. 这种静默失败会让None悄悄流向下游代码极易产生难以排查的空指针类问题也不符合 Python 社区异常即错误的惯例。2.2 显式开启异常UseExceptions()调用gdal.UseExceptions()后绑定层会把错误翻译为 Python 异常 from osgeo import gdal gdal.UseExceptions() # 开启异常 gdal.Open(C:\\foo.img) Traceback (most recent call last): File stdin, line 1, in module RuntimeError: C:\foo.img does not exist in the file system, and is not recognized as a supported dataset name. 2.3 源码实现UseExceptions()到底做了什么绑定层的异常开关实现在 swig/include/python/python_exceptions.i 中。核心逻辑如下模块内部维护静态开关bUseExceptions与线程局部开关bUseExceptionsLocal见GetUseExceptions()/_SetExceptionsLocal()因此每个线程可以拥有独立的异常状态_UseExceptions()/_DontUseExceptions()会先调用CPLErrorReset()清空上次错误状态再置位/复位开关并把bUserHasSpecifiedIfUsingExceptions置为TRUE绑定的 SWIG%exception包装逻辑同文件约 204–245 行在开启异常时执行pushErrorHandler()用PythonBindingErrorHandler接管 CPL 错误回调调用结束后若CPLGetLastErrorType()为CE_Failure或CE_Fatal则抛出SWIG_RuntimeError即 Python 侧的RuntimeError特别地Open、OpenShared、OpenEx三个函数swig/include/gdal.i 同思路的%feature(except)包装在返回结果为 NULL时才抛异常——因为这三个函数返回 NULL 本身就是打开失败的强信号。一个值得注意的细节自 GDAL 3.7 起UseExceptions()/DontUseExceptions()作用于全部相关模块osgeo.gdal、osgeo.ogr、osgeo.osr、osgeo.gnm以及已安装 numpy 时的osgeo.gdal_array而在此之前只影响调用所在的模块。文档中的 docstring 明确标注了这一差异Note: prior to GDAL 3.7, this only affected the calling module。2.4 面向未来的兼容性GDAL 4.0 将默认开启异常文档特别给出警告计划在 GDAL 4.0 中默认启用异常。不希望在未来版本中收到异常的代码应当显式调用gdal.DontUseExceptions()关闭。因此当前版本下养成要么在入口显式UseExceptions()要么显式DontUseExceptions()的习惯可以避免未来升级带来的行为突变。此外模块还提供了gdal.ExceptionMgr()上下文管理器实现于同一文件约 295–355 行可在局部代码块中临时启用/恢复异常状态 print(gdal.GetUseExceptions()) 0 with gdal.ExceptionMgr(): ... # 此处异常已开启 ... print(gdal.GetUseExceptions()) 1 # 离开 with 块后状态被恢复 print(gdal.GetUseExceptions()) 0三、对象生命周期陷阱先删父对象再用子对象 → 崩溃或异常3.1 经典复现场景Band 与 Datasetband与dataset之间存在所有权关系band的正常工作依赖其所属dataset仍然存活。如果先删除dataset再使用band就会抛出令人困惑的异常 from osgeo import gdal dataset gdal.Open(C:\\RandomData.img) band dataset.GetRasterBand(1) del dataset # 触发 Python 垃圾回收dataset 被释放 band.Checksum() # 此时 band 的宿主已不存在 # TypeError: in method Band_Checksum, argument 1 of type GDALRasterBandShadow *注意在 GDAL 3.7 及更早版本中这种情况会导致进程崩溃crash而非异常较新版本绑定层为这类已知关系做了引用失效标记改抛异常但并未覆盖全部关系。3.2 隐蔽的变体临时对象、函数内局部变量同样的错误还可能以三种伪装出现变体 A——单行链式调用 print(gdal.Open(C:\\RandomData.img).GetRasterBand(1).Checksum()) # TypeError: in method Band_Checksum, argument 1 of type GDALRasterBandShadow *GetRasterBand()返回后gdal.Open(...)创建的 dataset 已无引用被提前回收随后Checksum()便访问到已失效的对象。变体 B——函数返回子对象 def load_band(data_filename): dataset gdal.Open(data_filename) return dataset.GetRasterBand(1) band load_band(c:\\RandomData.img) print(band.Checksum()) # TypeError: in method Band_Checksum, argument 1 of type GDALRasterBandShadow *load_band返回时dataset局部变量即被销毁返回值band随之失效。变体 C——Feature 与 Geometry该问题不限于 Band/DatasetOGRFeature拥有其OGRGeometry任何子对象依赖父对象存活的关系都可能触发同类崩溃。文档原文强调目前不存在这类关系的完整清单只能由程序员自行留意。3.3 正确写法让父对象活到子对象用完之后避免单行链式调用显式持有dataset变量若确需把band传出函数应同时保留 dataset 引用例如在闭包、类属性或返回元组中一并携带检查代码中对del、None赋值以及函数边界处的对象存活情况。四、OGR 层给活跃图层加字段会导致崩溃当从某图层定义派生出的 Feature 仍然活跃时向该图层新增字段会使既有 Feature 失效随后访问即崩溃 feature lyr.GetNextFeature() field_defn ogr.FieldDefn(foo, ogr.OFTString) lyr.CreateField(field_defn) # 既有 feature 自此失效 feature.DumpReadable() # 段错误segfault Python crashes 该行为对应历史 ticket #3552trac.osgeo.org/gdal/ticket/3552属于已知且未修复的崩溃类问题。规避方式先完成所有CreateField再迭代/使用 Feature若必须在迭代中加字段先释放已取出的 Feature 引用del或赋None再继续。五、属性过滤器的隐蔽语义SetAttributeFilter()只作用于GetNextFeature()5.1 底层事实从 ogr/ogrsf_frmts/generic/ogrlayer.cpp 的OGR_L_SetAttributeFilter()实现可见其语义注释明确写到该查询字符串用于经OGR_L_GetNextFeature()顺序取要素时过滤to be used when fetching features via the OGR_L_GetNextFeature() function. Only features for which the query evaluates as true will be returned.安装查询会重置当前读取位置installing a query string will generally result in resetting the current reading position (ala OGR_L_ResetReading())查询格式为受限的 SQL WHERE 子句对于 RDBMS 后端驱动如 PostgreSQL、SQLite、GeoPackage可能使用数据库原生能力解析能力范围比 OGR SQL 更广swig/include/python/docs/ogr_layer_docs.i 中SetAttributeFilter的 docstring 亦与之一致。Python 侧的SetAttributeFilter在 swig/include/ogr.i 中直接转发到OGR_L_SetAttributeFilter()。5.2 陷阱GetFeature()与GetFeatureCount()的不对称由于过滤只作用于GetNextFeature()若改用按 ID 随机读取的GetFeature()仍可访问过滤条件之外的数据GetFeatureCount()会尊重过滤器返回正确的计数。二者组合使用会产生隐蔽的逻辑错误 lyr inDataSource.GetLayer() lyr.SetAttributeFilter(PIN 0000200001) # 过滤器只匹配一条记录 for i in range(0, lyr.GetFeatureCount()): ... feat lyr.GetFeature(i) ... print(feat) # 打印的是图层中的第一条要素而非被过滤的那条 ...GetFeatureCount()返回过滤后的数量 1但GetFeature(0)按 ID 取回的是图层第一条要素与过滤器无关。推荐做法遍历 Layer 对象本身或使用GetNextFeature()不要用计数 按 ID 取的组合。六、Destroy()的迷思多数场景不应调用落盘用上下文管理器或Close()6.1 为什么不应该主动调用Destroy()部分老教程如文档引用的 OSPy 讲义建议在特定时机调用Destroy()。但主动调用Destroy()会强制销毁底层原生对象而正常情况下这些对象会在 Python 垃圾回收、无剩余引用时自动销毁主动调用既不必要还可能诱发空悬引用。6.2 什么时候必须显式释放数据落盘的确定性存在一个例外场景gdal.Dataset与ogr.DataSource的内容只有在其底层原生对象被销毁时才保证写入磁盘。若不关心确切落盘时机则无需处理若需要在特定时间点确保数据写出文档推荐方式一首选上下文管理器with块GDAL 3.8 起支持from osgeo import ogr with ogr.GetDriverByName(ESRI Shapefile).CreateDataSource(/tmp/test.shp) as ds: lyr ds.CreateLayer(test) feat ogr.Feature(lyr.GetLayerDefn()) feat.SetGeometry(ogr.CreateGeometryFromWkt(POINT (1 2))) lyr.CreateFeature(feat) # 离开 with 块后ds 的内容已写入磁盘方式二函数内需要提前落盘时调用Close()在无法使用with的场景例如对象需要在函数内部销毁可调用Close()方法。其绑定实现见 swig/include/ogr.i 的OGRDataSourceShadow类Close()直接调用GDALClose(self)SyncToDisk()调用OGR_DS_SyncToDisk而栅格侧GDALDataset的FlushCache()调用GDALFlushCache同文件 961–962 行附近。注意在 GDAL 3.8 之前上下文管理器与Close()尚不可用只能对ogr.DataSource使用Destroy()或通过del/ 赋None强制垃圾回收。方式三仅限栅格/矢量的尽力而为落盘文档明确指出部分驱动可借FlushCache()栅格或SyncToDisk()矢量实现间歇性保存但两者都不保证数据真正写盘因此首选仍是上下文管理器或Close()。七、自定义错误处理器中抛出的异常不会被捕获7.1 问题错误处理器运行在独立线程异常无法传回主线程Python 绑定允许通过gdal.PushErrorHandler()注册一个 Python 可调用对象作为 CPL 错误处理器对应历史 ticket #4993。但这些处理器似乎在独立线程中被调用ticket #5186其中抛出的异常不会传播回主线程。因此下面这种用异常同时捕获 warning 与 error的写法无效from osgeo import gdal def error_handler(err_level, err_no, err_msg): if err_level gdal.CE_Warning: raise RuntimeError(err_level, err_no, err_msg) # 该异常无法传回主线程 if __name__ __main__: gdal.PushErrorHandler(error_handler) gdal.Error(gdal.CE_Warning, 2, test warning message) gdal.PopErrorHandler()7.2 正确方案用对象记录错误状态 UseExceptions()互补把错误信息记录到对象属性再配合UseExceptions()对 CE_Failure的错误抛异常即可同时捕获 warning 与 errorfrom osgeo import gdal class GdalErrorHandler: def __init__(self): self.err_level gdal.CE_None self.err_no 0 self.err_msg def handler(self, err_level, err_no, err_msg): self.err_level err_level self.err_no err_no self.err_msg err_msg if __name__ __main__: err GdalErrorHandler() gdal.PushErrorHandler(err.handler) gdal.UseExceptions() # 对 gdal.CE_Failure 的错误抛异常 assert err.err_level gdal.CE_None, error level 初始值为 0 try: # 演示 warning 的处理不应抛异常但错误状态被记录 try: gdal.Error(gdal.CE_Warning, 8675309, Test warning message) except Exception: raise AssertionError(Operation raised an exception, this should not happen) else: assert err.err_level gdal.CE_Warning, ( The handler error level should now be at warning) print(Handled error: level{}, no{}, msg{}.format( err.err_level, err.err_no, err.err_msg)) # 演示 error 的处理应抛异常且异常消息与记录的 err_msg 一致 try: gdal.Error(gdal.CE_Failure, 42, Test error message) except Exception as e: assert err.err_level gdal.CE_Failure, ( The handler error level should now be at failure) assert err.err_msg e.args[0], raised exception should contain the message print(Handled warning: level{}, no{}, msg{}.format( err.err_level, err.err_no, err.err_msg)) else: raise AssertionError(Error message was not raised, this should not happen) finally: gdal.PopErrorHandler()此模式对既要捕获 warning 又要捕获 error的完整场景如文档引用的 GIS.SE 讨论帖问题是标准解法。八、由外部软件行为导致的 Gotchas以下两类问题根源不在 GDAL 本身而是与其他软件的 ABI / 运行时行为相关8.1 升级或降级 numpy 后绑定崩溃GDAL Python 绑定的大部分由 C 实现而 numpy 核心由 C 实现绑定层通过 numpy 的ABI与 numpy 的 C 数据结构交互。这要求绑定在编译时使用与运行时一致版本的 numpy 头文件numpy 的数据结构在不同版本间可能变化导致新版本 numpy 与旧绑定在二进制层面不兼容必须重编译绑定重编译后通常也不再兼容旧版本 numpy。因此若使用预编译的 GDAL Python 绑定包文档示例为 Windows 下的 gisinternals SDK 包务必确认其编译所用的 numpy 版本并在本机安装相同版本的 numpy。8.2 ArcGIS 进程内in-process工具中使用绑定首次成功、之后TypeErrorArcGIS 允许创建基于 Python 的自定义地理处理工具。自 ArcGIS 9.3 起工具可选择在 ArcGIS 进程内ArcCatalog.exe / ArcMap.exe或独立python.exe工作进程中运行但 ArcGIS 在运行进程内工具时存在 Bug首次运行正常之后直到重启 ArcGIS 进程前可能反复出现TypeError例如band.ReadAsArray()报TypeError: in method BandRasterIONumpy, argument 1 of type GDALRasterBandShadow *该问题根因在 ArcGIS对应历史 ticket #3672可通过该 ticket 查阅完整细节与规避建议例如改用独立工作进程模式运行工具。九、总结Python 绑定避坑清单陷阱本质推荐规避方式默认不抛异常C 层错误码模型 绑定层默认静默入口调用UseExceptions()考虑 GDAL 4.0 默认开启的兼容性可显式DontUseExceptions()删除父对象后再用子对象Band/Dataset、Feature/Geometry 等C 对象所有权链父对象保持存活直至子对象使用完毕避免单行链式调用活跃 Feature 存在时CreateFieldOGR 层未修复的崩溃#3552先建字段后迭代要素SetAttributeFilter()后GetFeature()取到未过滤数据过滤仅作用于GetNextFeature()ogr/ogrsf_frmts/generic/ogrlayer.cpp 中的OGR_L_SetAttributeFilter()语义用 Layer 迭代或GetNextFeature()勿用计数按 ID 取滥用Destroy()破坏自动垃圾回收机制用with上下文管理器或Close()GDAL 3.8FlushCache()/SyncToDisk()不保证落盘自定义错误处理器中 raise 的异常丢失处理器运行于独立线程#5186用对象记录err_level/err_no/err_msgUseExceptions()互补numpy 版本不匹配崩溃numpy C ABI 变化预编译包须匹配编译时 numpy 版本ArcGIS 进程内工具TypeErrorArcGIS 自身 Bug#3672使用独立工作进程模式最后重申文档作者的观点这份清单不是 Bug 报告渠道——若确认是 Bug 应开 ticket 并同步到 gdal-dev 邮件列表若只是不 Pythonic但属于设计使然的行为可在 gdal-dev 讨论改进可能性。大部分条目之所以保持现状是因为修复它们要么成本过高要么会破坏向后兼容。延伸阅读本仓库相关实现文件包括 swig/include/python/python_exceptions.i异常开关与错误处理器、swig/include/ogr.iDataSource/Feature/Band 等对象绑定、swig/include/python/docs/ogr_layer_docs.iLayer 方法 docstring、ogr/ogrsf_frmts/generic/ogrlayer.cppOGR_L_SetAttributeFilter实现以及 swig/python 目录下丰富的 Python 示例脚本如 validate_cloud_optimized_geotiff.py可作为正确用法的实战参考。赞分享GIS遥感数据工程【免费下载链接】gdalGDAL is an open source MIT licensed translator library for raster and vector geospatial data formats.项目地址https://gitcode.com/gh_mirrors/gd/gdal点击查看免费下载相关推荐Midway 应用生命周期全解ILifeCycle 接口、对象生命周期与超时机制实战Midway 应用生命周期全解ILifeCycle 接口、对象生命周期与超时机制实战 本篇围绕 Midway midwayjs/core 的 应用生命周后端微服务云原生macOS虚拟打印机PDF转换工具告别繁琐文档转换的终极方案macOS虚拟打印机PDF转换工具告别繁琐文档转换的终极方案 在macOS工作环境中我们经常需要将各种文档转换为PDF格式进行分享、归档或打印。无论是网页内驱动开发操作系统DBeaver OSGi服务引用绑定控制服务绑定与解绑的生命周期DBeaver OSGi服务引用绑定控制服务绑定与解绑的生命周期 在企业级应用开发中服务组件的生命周期管理往往决定了系统的稳定性与资源利用率。DBeaver数据库客户端桌面应用数据库上一篇【亲测免费】 ZAP社区脚本项目教程下一篇CANN/asc-devkit NDLayoutFormat结构体创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考