资讯动态

GDAL创建矢量图层两种方式:标准流程与快捷创建

发布时间:2026/10/13 13:07:41 来源:尧图企业网站定制
1. 为什么老有人问 GDAL 创建矢量图层搞GIS开发的人尤其是用C或者Python跟GDAL打交道比较多的几乎都绕不过“创建矢量图层”这个操作。但就这么个基础功能反而成了很多人困惑的起点。原因其实很简单GDAL的文档虽然全但对新手并不友好各种概念堆在一起——Driver、DataSource、Layer、Feature、Geometry——第一次接触的人很容易被绕晕。再加上网上不少教程要么只讲其中一种方式要么就是一上来就甩代码完全不解释为什么这么写结果就是很多人照着抄能跑换个格式就懵了。我自己最早接触这个功能的时候也踩过坑。当时的需求很简单把一批坐标点写成Shapefile。我照着网上找的示例代码吭哧吭哧写完确实能生成文件但回头想改造成写GeoJSON、写PostGIS甚至写GPKG就发现原来的代码完全套不上。后来才意识到GDAL创建矢量图层这事表面上是在“创建一个文件”本质上其实是在“操作一个数据源”而这个数据源跟输出格式强相关。搞懂了这一层后面所有困惑基本都能解开。这篇文章想聊的就是GDAL创建矢量图层最常见的两种方式。一种是最标准的“Driver驱动 数据源 图层”三层结构适合绝大多数文件类格式另一种是通过创建数据源时直接指定扩展名或格式让GDAL自己去推断驱动代码更省事但控制力弱一些。两种方式各有各的适用场景我在实际项目中都大量用过所以今天就把它们的差异、实现细节和踩坑心得一次性讲透。2. 动手前的底层认知GDAL抽象模型到底在说什么在写任何一行创建图层的代码之前有些底层概念必须先理顺。我见过太多人代码写得飞起但问他“Driver、DataSource、Layer分别是什么关系”就答不上来。这种状态很危险因为GDAL的设计其实非常抽象如果不理解它的抽象模型一旦遇到多图层文件、多格式输出、属性读写这些稍微复杂一点的需求就会立刻卡壳。2.1 从一份炒饭外卖说起理解三个核心对象打个比方。假设你去点一份炒饭外卖。你打开外卖App找到一家你喜欢的餐厅然后从它的菜单里点一份炒饭。这个过程里其实藏了三个角色餐厅相当于Driver也就是驱动。GDAL通过Driver知道“这家店是做哪种菜系的”——对应到GIS里就是Shapefile驱动、GeoJSON驱动、GPKG驱动等等。每一种驱动负责一种格式的读写。这家餐厅的门店相当于DataSource也就是数据源。你找到餐厅后是在这个具体的门店里点单。对应到GDAL里一个DataSource就是一个具体的文件、数据库连接或者内存数据集。菜单里的炒饭相当于Layer也就是图层。门店会有很多菜品对应到GIS里同一个数据源比如一个GPKG文件里可以包含多个图层Layer每个图层里有几何对象和属性字段。这个类比虽然简单但它能帮你建立起最重要的一个认知GDAL操作矢量数据核心顺序永远是“先定位驱动再打开数据源然后在数据源里操作图层”。凡是能套进这个顺序的格式GDAL基本都支持凡是套不进去的大概率就是你搞错了概念。2.2 两种创建方式的核心差异控制力与代码量的取舍接下来要说重点了创建矢量图层的两种方式到底差在哪。第一种方式是我最推荐的也是GDAL官方示例里最常见的方式。它分三步走——先用GetDriverByName拿到对应格式的驱动再用驱动创建DataSource最后在DataSource上调用CreateLayer创建图层。整个过程完全可控每一步都能拿到中间对象想做错误处理也方便。缺点是代码稍微多几行但对于正式项目来说这点冗余完全是值得的。第二种方式是直接用drv.CreateDataSource或者SHPPointer这种快捷方式把文件路径传进去GDAL会根据扩展名自动判断该用哪个Driver。这种方式写起来极其省事两行代码就能创建一个图层。但代价是你对底层驱动的控制力变弱了比如你想指定创建选项像是Shapefile的编码方式、GeoJSON的坐标精度快捷方式就不太好用了。我在实际工作中其实是两种混着用的快速验证想法的时候用第二种写正式工具或者要发布给团队用的时候一定改成第一种。为什么因为第二种方式依赖扩展名推断一旦文件名起得不规范或者输出格式比较冷门GDAL推断失败就会直接报错而且报错信息往往让人摸不着头脑。第一种方式虽然代码多几行但每一步都明确指定了驱动出问题也好排查。2.3 什么时候该用哪种方式一张表给你答案使用场景推荐方式原因快速验证、临时生成测试文件方式二快捷创建代码少马上能跑正式项目、工具开发方式一标准流程控制力强可指定创建选项输出格式不确定、由用户输入决定方式一可提前检测驱动是否存在写入GeoJSON、GPKG等多图层格式方式一方便管理多个图层需要指定空间参考、字段类型、编码方式一每种驱动都能单独配置纯写一个简单CSV之类的文件方式二简单场景不需要复杂配置表格里提到的“创建选项”是个关键概念。每种GDAL驱动都支持一组特定的CreateOption比如Shapefile驱动可以指定编码ENCODINGGeoJSON驱动可以指定坐标精度COORDINATE_PRECISION。这些选项只有在用第一种方式、拿到Driver之后才方便设置。这是我在项目里吃过亏的地方用快捷方式写GeoJSON默认精度不够结果生成的文件坐标被截断数据精度丢失排查了半天才发现是创建选项的问题。3. 方式一详解标准流程逐步拆解这一节是全文的重头戏。标准流程看着简单但每一步都藏着不少细节。我会从零开始把创建矢量图层的全流程拆开来讲包括代码结构、每个参数的含义、以及最容易出问题的地方。3.1 第一步拿驱动但不是所有驱动都叫Shapefile拿驱动这一步代码就一行from osgeo import ogr driver ogr.GetDriverByName(ESRI Shapefile)但这一行背后我建议你先做一件事确认驱动存在。GDAL不是所有格式都内置支持的有些格式需要额外的库或者插件。如果驱动不存在的GetDriverByName会返回None你后面所有代码都会在调用时直接崩溃。我一般会这样写driver ogr.GetDriverByName(ESRI Shapefile) if driver is None: raise RuntimeError(ESRI Shapefile 驱动不可用请检查GDAL安装)这里有个小细节值得说。你可能会好奇“ESRI Shapefile”这个名字是哪儿来的。GDAL里每个驱动都有自己的名称字符串Shapefile驱动叫“ESRI Shapefile”GeoJSON驱动叫“GeoJSON”GPKG驱动叫“GPKG”。这些名字是固定的不能随便写。最稳妥的办法是运行ogrinfo --formats命令查看当前GDAL支持的所有驱动列表里面显示的名字就是合法的驱动名。3.2 第二步创建数据源区分“创建新文件”和“覆盖旧文件”拿到驱动之后下一步就是创建DataSource。这一步最容易被忽视的是它的第二个参数——是否允许覆盖。看代码data_source driver.CreateDataSource(output.shp)这样写如果output.shp已经存在GDAL会报错提示文件已经存在。很多人第一次遇到这个报错会很懵因为错误信息里只说“File already exists”但没告诉你解决办法。解决办法是在CreateDataSource的时候传一个数字作为第二个参数。GDAL约定传0表示不允许覆盖已有的文件传1表示允许覆盖data_source driver.CreateDataSource(output.shp, 0) # 不允许覆盖 data_source driver.CreateDataSource(output.shp, 1) # 允许覆盖我在写工具的时候一般会提供两个选择如果图层存在就追加如果不存在才新建。这里又要多说一句Shapefile一个文件只能对应一个图层但GPKG这种格式一个文件可以对应多个图层。所以在用Shapefile时如果你的需求是“往已有的文件里加图层”那是做不到的只能新建文件。另外创建DataSource时还有个跟文件系统相关的坑父目录必须存在。GDAL不会帮你自动创建目录。如果你的输出路径是/data/output/2024/result.shp但/data/output/2024/这个目录不存在GDAL同样会报错。这个问题我在自动任务里遇到过好多次解决办法是在创建数据源之前先确保父目录存在import os output_dir /data/output/2024 os.makedirs(output_dir, exist_okTrue) data_source driver.CreateDataSource(os.path.join(output_dir, result.shp))3.3 第三步创建图层SpatialReference和GeometryType怎么选数据源有了接下来就是核心中的核心——创建图层layer data_source.CreateLayer(points, srs, ogr.wkbPoint)三个参数分别是图层名、空间参考SpatialReference、几何类型GeometryType。每一个单独拿出来说都能写一大篇我捡最关键的讲。先说空间参考。这个参数是可选的可以传None。但传None意味着图层没有空间参考信息很多GIS软件打开后会提示“unknow spatial reference”给后期投影转换、叠加分析带来麻烦。我建议只要条件允许一定显式指定空间参考。最常见的是用EPSG代码from osgeo import osr srs osr.SpatialReference() srs.ImportFromEPSG(4326) # WGS84经纬度这里也提醒一句4326是WGS843857是Web墨卡托这两个是最常用的。国内项目还经常用GCJ02火星坐标或者CGCS2000不同坐标系之间一定要搞清楚再往下走不然后期数据对不上非常痛苦。再说几何类型。ogr模块里定义了一系列常量常用的有ogr.wkbPoint点ogr.wkbLineString线ogr.wkbPolygon面ogr.wkbMultiPoint多点ogr.wkbMultiLineString多线ogr.wkbMultiPolygon多面这些类型选错会有什么后果举个真实案例。有一次我想创建一个点图层但GeometryType误写成了ogr.wkbPolygon结果创建图层本身没报错但往里面写入点要素时GDAL直接报错说几何类型不匹配。这个错误看起来是“写入”阶段的问题但根子在“创建图层”阶段。3.4 第四步定义字段绕不开的FieldDefn图层创建好之后如果不定义任何字段这个图层就是一个只能存几何、不能存属性的“空壳子”。所以在写要素之前必须先把表结构定义好。字段的定义需要用到FieldDefn一个字段就是一个FieldDefn对象。看代码field_defn ogr.FieldDefn(name, ogr.OFTString) field_defn.SetWidth(12) layer.CreateField(field_defn)第一行创建字段定义指定字段名和类型。第二行设置字段宽度可选。第三行真正把字段添加到图层上。字段类型这里容易犯的错是“所有属性都写字符串”。短期看没问题后期做属性查询、统计、连接的时候就会发现例子不好用了。常用的字段类型有ogr.OFTString字符串ogr.OFTInteger整数ogr.OFTReal浮点数ogr.OFTDate日期ogr.OFTTime时间ogr.OFTDateTime日期时间我见过有人在属性里存时间戳直接用字符串写“2024-01-15 10:30:00”查询的时候用字符串比较。能用但效率低而且遇到时区转换就完全没法处理了。不如一开始就用ogr.OFTDateTime类型后期怎么处理都方便。还有一个容易被忽略的点定义字段时尽量把字段宽度和精度设置到位。比如经纬度坐标如果存成浮点数默认精度可能只能存到小数点后6位对于高精度定位数据来说这已经不够了。可以在FieldDefn里用SetWidth和SetPrecision调整coord_defn ogr.FieldDefn(longitude, ogr.OFTReal) coord_defn.SetWidth(12) coord_defn.SetPrecision(8) layer.CreateField(coord_defn)3.5 第五步写要素创建Feature和Geometry的配合图层和字段都定义好了接下来就是写入要素。这一步的逻辑是创建一个Feature对象给它设置几何对象和属性值然后通过图层写入。代码结构是这样feature ogr.Feature(layer.GetLayerDefn()) # 构造几何对象 point ogr.Geometry(ogr.wkbPoint) point.AddPoint(116.391, 39.907) # 北京坐标 feature.SetGeometry(point) feature.SetField(name, 测试点) layer.CreateFeature(feature) # 释放资源 feature None这里有几个必须注意的坑都是我自己踩过或者帮别人排查过的第一个坑是忘了处理旧Geometry。如果你在循环里反复创建Geometry然后SetGeometry到Feature上Python的引用计数有时候会把这事情搞得很复杂。严格来说SetGeometry会复制一份几何数据但如果你手动new了Geometry又给Feature在用完后应该用feature.SetGeometry(None)或者直接feature None来释放。否则在小内存服务器上批量写入几十万条数据内存会一路飙上去。第二个坑是字段索引和字段名的选择。SetField既可以传字段名也可以传字段索引但传字段索引更容易出错。如果字段顺序后来调整了索引就变了代码就会把属性写到错误的字段里。我建议永远用字段名字符串。第三个坑其实是编码问题。GDAL默认情况下Shapefile的属性编码是ISO-8859-1不是UTF-8。如果你往字段里写中文再用ArcGIS或者QGIS打开看到的很可能是乱码。解决办法是在创建DataSource的时候指定编码或者用创建选项。我一般这样处理data_source driver.CreateDataSource(output.shp) layer data_source.CreateLayer(points, srs, ogr.wkbPoint, options[ENCODINGUTF-8])这里CreateLayer的第四个参数是创建选项列表。这个参数看起来不起眼但恰恰是很多人中文乱码的根源。3.6 资源释放一个必须养成肌肉记忆的动作最后一步也是经常被忽略的一步就是释放资源。在Python里由于有引用计数和垃圾回收很多人觉得“反正程序跑完就自动释放了”于是就不管了。这个态度在写小脚本时可能无所谓的但在写长时间运行的批处理任务时会出现文件占用、句柄泄漏之类的问题。我用GDAL写批处理工具时习惯是在用完DataSource之后显式关闭data_source None这一行代码的作用是关闭数据源文件确保所有数据已经落盘。如果你不主动置None在某些情况下尤其是批量创建多个文件会出现最后一个文件内容为空的现象。原因是数据还在缓冲区里没有刷到磁盘。置None相当于强制flush并关闭文件句柄。我在之前的一次数据处理任务中跑了几百次循环创建Shapefile全程没有置None结果发现有一部分输出文件是0字节。排查了很久最后发现就是资源没有及时释放导致的。从那以后data_source None成了我GDAL代码里最执着的习惯之一。4. 方式二详解覆盖格式之争GDAL的快捷方式讲完标准流程再来看另一种方式。这种方式主要适用于“GDAL根据文件扩展名自动推断驱动”的场景。听起来就很省事但代码怎么写、有什么坑let me一个一个说。4.1 两行代码搞定图层创建的魔法前面方式一里我们花了大量篇幅区分Driver、DataSource、Layer。如果只是临时用一下不想管那么多直接这样写就行from osgeo import ogr ds ogr.GetDriverByName(ESRI Shapefile).CreateDataSource(quick.shp) layer ds.CreateLayer(quick, None, ogr.wkbPoint)等一下这不还是指定了Driver吗对的方式二的重点在另一个入口直接用数据源接口不显式指定Driver。在GDAL里ogr.Open这个函数既可以打开已有的数据源也可以创建新的数据源。只不过创建的时候需要传入一个参数告诉GDAL要去创建而不是打开ds ogr.Open(quick.shp, 1) # 第二个参数1表示可写有意思的是在较新版本的GDAL中Open函数并不能用来创建数据源。真正用于快捷创建的是各驱动对象本身。所以严格说在Python的GDAL里“不指定驱动直接创建”并不是一个真正的入口点。这也就是为什么我说方式二更像是一种“简化的写法习惯”——它省去了检查驱动的步骤但仍然绕不开Driver。那真正的“方式二”是什么呢我后来在项目里和同行交流发现大家常说的还有另一种含义使用抽象的Driver接口通过DataSource创建完图层后不关心图层概念的内部实现。说白了就是“反正gdal会自己找到合适的驱动我只要数据格式对就行”。4.2 让GDAL猜文件格式的代价如果GDAL要“猜”文件格式它会根据文件扩展名查表。这听起来很智能但也带来了两个明显的问题。第一个问题是扩展名决定格式文件名必须规范。如果用户输出的是.abc这种GDAL不认识的扩展名GDAL会直接创建一个“ESRI Shapefile”的目录然后把你所有的数据当Shapefile处理出来的东西乱七八糟。开发工具时我建议对用户输入的文件名做校验限制扩展名在白名单里面。第二个问题是格式不同内部行为不同。扩展名虽然是.shp但你让GDAL猜它默认用Shapefile驱动如果全小写写成.shp驱动不变但如果输出的是.shp.xmlGDAL就不知道该怎么办了。这种因为文件名不规范导致的“猜错格式”排查起来比方式一的Bug要痛苦得多因为报错信息往往不直接指向扩展名问题。所以我的结论是方式二适合“我自己知道在做什么”的时候用——比如一次性的调试脚本、写个demo跑一遍。但在正式系统、长时间运行的批处理服务里我还是建议老老实实写成方式一因为方式一至少每一步都明确、可控、可排查。4.3 快捷创建在GeoJSON和内存数据源里的表现刚才说的都是文件格式其实方式二还有个很有意思的应用场景——内存数据源。GDAL支持创建内存数据源数据不落盘全部驻留在内存中适合临时计算、中间传输driver ogr.GetDriverByName(Memory) ds driver.CreateDataSource(temp) layer ds.CreateLayer(layer, None, ogr.wkbPolygon)这里虽然指定了驱动但“Memory”这个驱动本身就是一种快捷机制不需要文件路径不需要检查目录创建完直接用用完直接丢弃。在数据处理管道里中间结果用内存数据源是非常好的实践能避免大量临时文件的读写开销。另外一个常见的快捷应用就是GeoJSON。GeoJSON文件本身只能存一个图层没有多个图层的概念。如果你用方式一创建DataSourceGDAL会警告说“GeoJSON只支持单图层”然后把数据强行塞进去。用方式二“猜格式”的方式往往这个警告会被吞掉数据写进去你打开看啥都有了。但如果你对坐标精度有要求还是得回到方式一因为GeoJSON驱动默认的输出精度是有限制的。4.4 什么时候无脑用快捷方式什么时候别碰我试着给个判断标准基本是面向需求反推的临时脚本、demo验证、调试用方式二效率优先。正式ETL管道用方式一可维护性优先。文件格式由用户输入决定用方式一先GetDriverByName判断驱动是否存在再创建。需要自定义字段宽度、精度、编码用方式一方式二在这些方面几乎没有控制力。反复创建、覆盖同一个文件方式一更稳因为有是否覆盖的参数控制。纯粹在内存中做中间计算用Memory驱动本质上属于第二种快捷方式的思路。从这些场景能看出来后者的使用频率其实并没有那么低但它适合的场景很聚焦。如果你把它用在错误的地方比如在正式服务里依赖扩展名推断驱动迟早会在某个凌晨收到报警。5. 两种方式对比与性能考量前面把两种方式的实现细节都拆过了这里再集中对比一下顺便聊聊性能问题。因为很多人会忽略创建图层的方式也会影响数据写入的效率。5.1 控制力、代码量与适用格式的横向对比对比维度方式一标准流程方式二快捷创建代码量较多7~10行较少3~5行驱动控制力强可指定支持的创建选项弱主要是让GDAL自己推断错误定位清晰哪一步出错基本能定位模糊可能直接抛不相关的错误对格式的适配性高通用、适合多种格式低适合常见、默认选项够用的格式资源释放需要显式处理同样需要但更容易被忽略推荐使用场景正式项目、多图层、定制需求临时验证、简单输出这个表格就是我心里面的标准答案。你在写代码的时候可以一边写一边对照这个表基本不会选错。5.2 批量写入时事务控制才是性能分水岭创建图层的方式跟性能有没有关系严格说创建过程本身的耗时差距可以忽略真正的性能差距出现在“写入大量要素”的时候。而这跟图层创建时有没有开事务直接相关。默认情况下通过GDAL创建图层后是一边写一边落盘的。每写入一个Feature文件系统都要同步一次。对于几千个点的数据集这没什么问题但如果是百万级的点云数据这种“边写边落盘”的模式会慢到怀疑人生。解决办法是在创建图层后、批量写入前开启事务layer.StartTransaction() try: for i in range(1000000): # 创建Feature并写入 pass layer.CommitTransaction() except Exception: layer.RollbackTransaction() raise事务的作用是让GDAL把多次写入先缓存在内存/缓冲区里直到CommitTransaction才真正落盘。这一块的处理对性能的提升是指数级的。我实测过百万级点写入Shapefile开不开事务速度差距能到10倍以上。尤其是配合方式一的标准流程事务控制带来的收益会非常明显。有些格式本身就有事务支持比如GPKG、PostGIS有些格式事务支持比较弱比如纯CSV。但Shapefile是支持事务的所以在大批量写入时事务控制绝对是个不可忽视的操作。5.3 内存与刷盘策略的经验谈除了事务控制还有内存控制。这里说的内存不是Python进程的内存而是GDAL内部用于缓冲写入数据的容量。如果准备写入的数据量超过了默认缓冲限制GDAL会自动把缓冲区里的内容刷到磁盘这个刷盘动作如果频繁触发同样会影响性能。解决办法有两个方向。一个是批量写入时控制要素数量比如每写5000个Feature就主动Open一个新的DataSource片段另一个是显式设置图层缓冲大小。不过某种程度上这两个方向都回到了“事务控制”的范畴里。我个人更推荐的做法是每次处理固定批次批量Commit。比如一次性读一万条记录写入后CommitTransaction再重新StartTransaction。这样的好处是内存峰值可控、崩溃恢复的位置明确不至于数据全部丢失。从代码上看标准流程和事务控制其实是一套组合拳。方式一因为每一步都有明确的中间对象在事务的Start和Commit之间插入操作非常自然。方式二因为简化了步骤往往容易被忽略。我用方式二临时处理小数据时倒是无所谓但一旦数据量上来还真得老老实实转回方式一加事务。6. 实战案例同一份数据用两种方式实现完整写入理论聊了这么多是时候来一个完整案例了。我构思了一个非常贴近实际场景的例子批量导入一组城市坐标点包含城市名、人口数量两个字段输出为Shapefile。分别用方式一和方式二实现过程中对比两个方式的代码结构和注意事项。6.1 标准流程实现从驱动到写要素的一次跑通先按方式一写一个可以直接复用的函数from osgeo import ogr, osr def create_point_layer_by_driver(output_path, data, epsg4326): # 1. 检查驱动 driver ogr.GetDriverByName(ESRI Shapefile) if driver is None: raise RuntimeError(Shapefile 驱动不可用) # 确认父目录存在 import os os.makedirs(os.path.dirname(os.path.abspath(output_path)), exist_okTrue) # 允许覆盖已有文件 ds driver.CreateDataSource(output_path, 1) if ds is None: raise RuntimeError(创建数据源失败) # 2. 空间参考 srs osr.SpatialReference() srs.ImportFromEPSG(epsg) # 3. 创建图层指定编码 layer ds.CreateLayer( cities, srs, ogr.wkbPoint, options[ENCODINGUTF-8] ) if layer is None: raise RuntimeError(创建图层失败) # 4. 字段定义 name_field ogr.FieldDefn(name, ogr.OFTString) name_field.SetWidth(32) layer.CreateField(name_field) pop_field ogr.FieldDefn(population, ogr.OFTInteger) pop_field.SetWidth(12) layer.CreateField(pop_field) # 5. 事务写入 layer.StartTransaction() try: for row in data: feature ogr.Feature(layer.GetLayerDefn()) point ogr.Geometry(ogr.wkbPoint) point.AddPoint(row[lon], row[lat]) feature.SetGeometry(point) feature.SetField(name, row[name]) feature.SetField(population, row[population]) layer.CreateFeature(feature) feature None layer.CommitTransaction() except Exception: layer.RollbackTransaction() raise # 6. 释放资源 ds None return output_path这段代码我基本是直接从项目里抽出来改的每一步都和前面讲的理论对齐。代码看起来长但胜在逻辑清晰每一步出错基本能定位到是哪一行。6.2 快捷流程实现同样的数据代码短一半再用方式二或者说“简化写法”来做一遍。注意我这里的简化并不是说完全不指定驱动而是把看起来繁琐的检查、选项配置省略掉直接靠GDAL根据常见需求生成from osgeo import ogr def create_point_layer_quick(output_path, data): driver ogr.GetDriverByName(ESRI Shapefile) ds driver.CreateDataSource(output_path, 1) # 没有空间参考没有编码选项 layer ds.CreateLayer(cities, None, ogr.wkbPoint) # 字段定义一样绕不开 name_field ogr.FieldDefn(name, ogr.OFTString) pop_field ogr.FieldDefn(population, ogr.OFTInteger) layer.CreateField(name_field) layer.CreateField(pop_field) for row in data: feature ogr.Feature(layer.GetLayerDefn()) point ogr.Geometry(ogr.wkbPoint) point.AddPoint(row[lon], row[lat]) feature.SetGeometry(point) feature.SetField(name, row[name]) feature.SetField(population, row[population]) layer.CreateFeature(feature) feature None ds None return output_path代码确实短了。但注意空间参考没指定编码没指定事务没开。这些“省略”在小数据量、临时使用的情况下问题不大可一旦数据量上去、或者后期要用GIS工具打开检查空间属性就会暴露短板。6.3 两种实现的结果对比与可维护性评价跑完上面两份代码我习惯性地做了几个验证用ogrinfo打开输出的Shapefile检查图层信息。用QGIS打开检查图层属性和坐标系。用另外一份点数据做叠加确认空间参考一致。验证结果很直观方式一产出的文件有完整坐标系信息属性字段编码正常中文不乱码叠加分析直接能用。方式二产出的文件也能打开但坐标系是未知属性编码在Windows下用QGIS打开存中文略微有风险。所以我给这两份代码的可维护性打个分的话方式一能拿9分方式二顶多7分。方式二的省事建立在“默认配置刚好满足需求”的前提下但真实项目里这种默认刚好满足的情况真的不多。7. 常见问题与排查技巧实录这个部分我把平时在项目里以及帮人答疑时最常碰到的问题整理出来。很多问题表面上看是“创建图层失败”根子上却是别的地方出了问题。7.1 “文件已存在”却不知道如何覆盖这是最常见的问题没有之一。看到错误信息是Failed to create file或者File already exists第一反应往往是“是不是我的文件名有问题”。其实GDAL在创建DataSource时如果目标文件已经存在默认行为就是拒绝覆盖。解决办法在前面已经讲过就是在CreateDataSource的第二个参数传1ds driver.CreateDataSource(output.shp, 1)另一个需要注意的情况是你用文本编辑器开着的output.shp也会导致创建失败。Windows平台下文件被其他程序占用时GDAL无论如何都创建不了。解决办法是先关闭占用文件的程序。7.2 创建了文件但内容是空的这个问题我在前文提到过最典型的场景是代码跑完了没报错但打开输出的Shapefile属性表里一条记录都没有或者文件大小只有几个字节。排查顺序是是否在写入后显式置ds None如果没有很可能是数据还在缓冲区。是否忘记调用layer.CreateFeature(feature)这个低级错误我见过不下三次。字段定义是否成功CreateField失败不会抛异常但它返回的值是OGRERR_NONE吗如果你忽略了返回值字段没建成功后续SetField等于白设。7.3 中文属性乱码乱码问题在GDAL中文圈子里是永恒的话题。核心原因前面讲过Shapefile默认属性编码不是UTF-8。对策是在创建图层时明确指定编码options[ENCODINGUTF-8]或者在创建DataSource时指定ds driver.CreateDataSource(output.shp, options[ENCODINGUTF-8])两种写法对应不同版本的GDAL API建议都用上双保险。如果已经写好了文件也可以用ogr2ogr来转编码ogr2ogr -lco ENCODINGUTF-8 output_utf8.shp input.shp这里多说一句GDAL 3.x之后对编码的处理比旧版本好了很多但依然不是默认UTF-8所以人工干预始终是需要的。7.4 几何类型不匹配的报错SetGeometry阶段报错说几何类型跟图层定义不一致这类错误90%都是创建图层时的GeometryType写错了。比如创建了wkbPolygon的图层却往里塞点要素。解决办法是在创建图层时想清楚自己到底要写什么类型的数据然后所有要素都保持一致。还有一种情况是图层创建时用了ogr.wkbUnknown这种类型理论上可以容纳任意几何但实际上很多驱动不支持。我在测试时用过一次写进去之后元数据乱得一塌糊涂。建议永远不要用wkbUnknown。7.5 创建GPKG或者PostGIS时的一些特殊问题如果你用同一套代码只是把Driver从“ESRI Shapefile”改成“GPKG”大概率不会出大问题。但有几个点不同GPKG驱动创建数据源时的选项更复杂比如可以指定VERSION1.0或者ADD_GPKG_OGR_CONTENTSNO这类选项。GPKG一个文件里可以有多个图层但图层名不能重复。PostGIS的驱动需要额外的数据库连接参数创建DataSource的时候就不是传文件路径了而是传PostgreSQL的连接字符串。从技术难度上PostGIS比文件格式高一档不止但创建图层的核心逻辑Driver → DataSource → Layer仍然不变。这也是为什么我强调要先理解抽象模型因为它不随格式变化。7.6 快速排查清单我自己写GDAL代码时每次遇到问题都会按下面这个顺序排查效率极高驱动是否存在GetDriverByName返回的是不是None文件路径的父目录是否存在目标文件是否被其他程序占用数据源创建是否允许了覆盖图层创建是否传对了空间参考和几何类型字段定义是否成功检查返回值写入要素时字段名是否拼写正确是否有事务未提交是否有ds None释放资源打开文件检查编码是否正常这个清单看起来长实际上每次排查都能帮你定位到具体问题。我建议直接抄走贴在你的工位上。8. 写在最后的一点经验分享GDAL创建矢量图层这件事本身并不复杂说到底是两个对象、两个方法的事情。可它之所以能难住这么多人根子在于GDAL的文档太“硬”了概念又多又缺少“为什么”的解释。我这几年写GDAL相关代码踩过最多的坑几乎都集中在格式适配、编码、资源释放和数据量上。格式适配要求你先想清楚输出给谁用——给ArcGIS用、给QGIS用、还是给Web系统用编码问题要求你养成好习惯一开始就指定UTF-8资源释放是一道肌肉记忆题写多了自然就不会忘数据量则逼着你老实开事务、看内存。至于两种创建方式我的态度很明确日常开发优先标准流程快捷方式留给自己玩的时候用。两者之间并不存在绝对的谁优谁劣关键是你能不能准确判断当前的场景需要多少控制力。控制力给多了代码冗余控制力给少了后面的排查成本加倍。这个度的把握就是经验本身。如果你正准备用GDAL做矢量数据的创建和转换我希望这篇能帮你少走几趟弯路。代码能跑只是一个开始能跑得稳、跑得久、换格式不慌才是真正把这套轮子用熟了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑