如果你正在用QGIS做二次开发,图层导出功能绝对是刚需。而QgsVectorFileWriter就是这个功能的核心实现类,它藏在qgsvectorfilewriter.h头文件里。我做过不少GIS项目,发现这个类就像个万能转换器——能把内存中的矢量图层变成磁盘上的shapefile、GeoPackage甚至PostgreSQL数据库表。
这个类有两种典型用法:一种是直接调用静态方法一次性导出整个图层,适合简单场景;另一种是实例化后逐步添加要素,适合需要精细控制的场景。但真正让我踩过坑的是它的版本迭代——从最初的writeAsVectorFormat到V2版,再到现在的V3版。就像手机系统升级一样,每个版本都有新特性和性能优化。
最新版的writeAsVectorFormatV3有个明显优势:它把所有的导出配置都封装进了SaveVectorOptions对象。这就像把散落的工具收进工具箱,用起来更顺手。实测在QGIS 3.20+的环境下,V3版的稳定性比老版本提升了不少,特别是处理大文件时不容易崩溃。
SaveVectorOptions这个配置对象就像图层导出的控制面板。我拆解过它的源码,发现这几个参数最值得关注:
driverName:这是格式选择的钥匙。比如要导出shp文件就设成"ESRI Shapefile",要转GeoPackage就用"GPKG"。有次我误写成"ESRI_Shapefile",结果导出直接失败,这个细节要注意。
fileEncoding:中文用户必看!默认编码是UTF-8,但如果你要兼容老系统,可能需要设为GBK。我遇到过属性表乱码的问题,就是编码没设对。
attributes:这个QgsAttributeList特别实用。比如图层有20个字段,但只需要导出前5个,就可以用options.attributes = {0,1,2,3,4};来控制。比导出后再删字段高效多了。
这里有个实际项目中的配置示例:
cpp复制QgsVectorFileWriter::SaveVectorOptions options;
options.driverName = "ESRI Shapefile";
options.fileEncoding = QTextCodec::codecForName("GBK");
options.attributes = {0, 2}; // 只导出第1和第3个字段
做数据迁移项目时,我经常需要批量导出上百个图层。这时候直接循环调用writeAsVectorFormatV3会有性能问题,后来摸索出几个优化方案:
多线程处理:用QgsTaskManager创建导出任务队列。每个QgsVectorFileWriterTask独立运行,实测速度能提升3-5倍。但要注意线程间不能共用同一个QgsCoordinateTransformContext对象。
内存缓存:对于需要重复导出的图层,先用QgsVectorLayerCache缓存数据。我在处理省级行政区划数据时,用缓存机制使导出时间从2分钟缩短到20秒。
错误处理:批量导出时一定要检查返回值。建议这样写:
cpp复制QString errorMsg;
if(QgsVectorFileWriter::NoError !=
QgsVectorFileWriter::writeAsVectorFormatV3(layer, fileName, transformContext, options, &errorMsg))
{
qDebug() << "导出失败:" << errorMsg;
}
坐标转换是导出时最容易出问题的环节。有次我给客户导出的shp文件总是偏移几百米,最后发现是transformContext没配置对。正确的做法是:
cpp复制QgsCoordinateReferenceSystem sourceCrs = layer->crs();
cpp复制QgsCoordinateReferenceSystem destCrs("EPSG:4326");
cpp复制QgsCoordinateTransformContext transformContext;
transformContext.addCoordinateOperation(sourceCrs, destCrs, QgsCoordinateTransformContext::HighestPrecision);
如果是批量导出不同坐标系的图层,一定要为每个图层单独创建transformContext。我后来封装了个工具函数自动处理这些逻辑,出错率大大降低。
除了基础导出,SaveVectorOptions还支持一些高阶功能:
空间过滤:通过设置extent参数,可以只导出特定范围内的要素。比如只要导出杭州市区的POI点:
cpp复制QgsRectangle hangzhouExtent(120.0, 30.0, 120.5, 30.5); // 杭州大致范围
options.filterExtent = hangzhouExtent;
SQL过滤:用datasourceOptions实现条件导出。例如只导出人口大于100万的城市:
cpp复制options.datasourceOptions = { "WHERE=population>1000000" };
字段别名:通过layerOptions可以重命名字段。这个在对接不同系统时特别有用:
cpp复制options.layerOptions = { "FIELDNAME=city_name AS name" };
虽然QgsVectorFileWriter支持多种格式,但各驱动有细微差别。我整理了几个常见格式的注意事项:
| 格式 | 最大字段名长度 | 字段类型限制 | 几何类型支持 |
|---|---|---|---|
| ESRI Shapefile | 10字符 | 不支持bool类型 | 单几何类型 |
| GeoPackage | 无限制 | 支持所有类型 | 混合几何类型 |
| CSV | 无限制 | 无类型约束 | 需单独存WKT |
特别提醒:导出到PostgreSQL时,如果表已存在需要先删除,否则会报错。可以这样处理:
cpp复制options.datasourceOptions = { "OVERWRITE=YES" };
在开发插件时,我总结出这些调试技巧:
bash复制export QGIS_DEBUG=1
对于大文件导出,建议先测试小范围数据。可以用QgsFeatureIterator的setLimit(100)方法限制要素数量。
内存监控很重要。导出前调用QgsVectorLayer::featureCount()预估数据量,超过50万要素建议分块处理。
如果导出速度突然变慢,检查是否启用了"创建空间索引"选项。这个选项对小文件有利,但大文件反而会拖慢速度。
最后分享一个真实案例:有次导出全国路网数据时程序卡死,最后发现是某个字段包含特殊字符导致。后来我养成了导出前先用QgsVectorLayer::isValid()检查图层的好习惯。