目录
一、问题描述
GeoPackage(简称“GPKG”)是本地化的轻量地理数据库,在一次使用GDAL函数创建GeoPackage的时候,直接用了原先写过的写Shapefile属性的代码(大致如下),结果发现运行速度非常慢、但如果是用来写Shapefile属性,速度比较快。经过排查最后发现是在调用SetFeature时消耗资源较多,怀疑是其内部是逐条提交的,但不能确认。于是向GDAL官网文档求助。
Feature feature;
while ((feature = oLayer.GetNextFeature()) != null) {
feature.SetField("RVI", 1);
oLayer.SetFeature(feature);
}
二、解决方法
查了GDAL文档中关于矢量驱动GPKG的参数描述,得知GPKG内部使用SQLite实现,SQLite是支持事务的数据库管理系统(DBMS),在默认情况下,GPKG内部数据更新是逐条提交的,这使得更新速度降低。
又查阅文档得知GDAL在写入矢量时可以使用StartTransaction()、RollbackTransaction()、CommitTransaction()来控制事务,但不是所有矢量驱动都支持事务,比如ESRI Shapefile就不支持。所还需要调用TestCapability()来测试图层使用具有处理事务的能力。
因此,如果想写一个基于GDAL的支持处理事务的数据写入程序,以下思路是一种实现:
下图是对手动提交事务逻辑的描述:
以上实现思路中,手动处理事务的方法与JDBC中批量更新数据的方法逻辑上是一样的, 如果常CRUD增删改查的话,就很容易理解。
三、代码例子
以下代码仅供学习交流,不建议直接用于生产环境。相对上述思路,代码中加入了一个是否启用手动处理事务的参数,该参数是用于对比试验,用来对比启用手动处理事务和不启用的耗时差距的。实际使用时,可以不必如此。
核心部分:
boolean supportTransactions = oLayer.TestCapability("transactions");
if (withTransaction && supportTransactions)
oLayer.StartTransaction();
for (int i = 0; i < featureList.size(); i++) {
Feature feature1 = featureList.get(i);
feature1.SetField(colName, 1.0);
oLayer.SetFeature(feature1);
if (withTransaction && supportTransactions && i % 20000 == 0) {
oLayer.CommitTransaction();
oLayer.StartTransaction();
}
}
if (withTransaction && supportTransactions)
oLayer.CommitTransaction();
在启动手动事务处理情况下,以上代码判断了图层是否支持事务,不支持事务的情况下,直接SetFeature应用要素,支持事务的图层将会每执行20000条提交一次修改结果。实际业务中,SetField部分的代码可能更加复杂。
完整代码:
public static void updateVector(String FileName, String strDriverName, String colName, boolean withTransaction) {
System.out.println("---------开始处理更新数据任务!---------");
// 注册所有的驱动
ogr.RegisterAll();
// 加载驱动
Driver oDriver = ogr.GetDriverByName(strDriverName);
if (oDriver == null) {
System.out.format("打开驱动失败!gdal错误:%s\n", gdal.GetLastErrorMsg());
return;
}
// 打开文件,获取数据源
DataSource oDS = oDriver.Open(FileName, 1);
if (oDS == null) {
System.out.printf("打开矢量文件%s失败!\n", gdal.GetLastErrorMsg());
return;
}
//打开图层,一般ESRI Shapefile只有一个图层
Layer oLayer = oDS.GetLayer(0);
if (oLayer == null) {
System.out.print("打开图层失败!\n");
return;
}
System.out.println("图层:" + oLayer.GetName());
int ct = (int) oLayer.GetFeatureCount();
System.out.println("图层要素数量:" + ct);
//遍历要素,一个要素可以理解为Shapefile属性表中的一行
List<Feature> featureList = new ArrayList<>();
Feature feature;
while ((feature = oLayer.GetNextFeature()) != null) {
featureList.add(feature);
}
System.out.println("读取图层要素到集合成功!");
long start = System.currentTimeMillis();
System.out.println("开始更新属性表!");
//有些矢量驱动不支持事务
boolean supportTransactions = oLayer.TestCapability("transactions");
System.out.format("图层是否支持事务:%s,用户是否想启用手动处理事务:%s\n", supportTransactions, withTransaction);
if (withTransaction && supportTransactions)
oLayer.StartTransaction();
for (int i = 0; i < featureList.size(); i++) {
Feature feature1 = featureList.get(i);
feature1.SetField(colName, 1.0);
oLayer.SetFeature(feature1);
if (withTransaction && supportTransactions && i % 20000 == 0) {
oLayer.CommitTransaction();
oLayer.StartTransaction();
}
}
if (withTransaction && supportTransactions)
oLayer.CommitTransaction();
long end = System.currentTimeMillis();
System.out.printf("更新完成,耗时%.3f秒\n", (end - start) / 1000.0);
//将图层数据保存到硬盘
oLayer.SyncToDisk();
oDS.SyncToDisk();
System.out.println("保存成功!");
oLayer.delete();
oDS.delete();
System.out.println("---------处理更新数据任务成功!---------");
}
调用代码:
public static void main(String[] args) {
String FileName = "E:\\性能测试\\grid1.gpkg";
String strDriverName = "GPKG";
updateVector(FileName, strDriverName, "RVI", false);
updateVector(FileName, strDriverName, "RVI", true);
}
在这里,一共调用修改属性的方法两次,第一次不启用手动事务处理,gdal会逐行提交修改内容,第二次启用了手动事务处理,gdal会每修改20000次,提交一次事务,结束时再提交一次。
运行结果:
可以发现启用手动处理事务后,写入属性的速度相对没有自动提交事务时大大提高了。
四、总结
使用java调用GDAL库来向矢量文件写入数据时,部分矢量驱动不支持事务,可以直接应用到图层,不影响写入效率,部分矢量驱动如GPKG、SQLite等支持事务,并需要手动提交事务才能提高效率。在写矢量数据时,通过判断图层是否支持事务,来决定是否手动提交事务,对提高写入效率有较大帮助。本文经过改进后的程序应该是更加优化后的修改矢量数据的通用程序。