QGIS二次开发基础 — 要素查询代码的优化问题

代码的效率问题是我们非常关注的核心问题之一,当你在说一个软件、一个第三方库在做某方面事情的效率不高的时候,一定要留意自己是否采用了正确的方式。凡事,先反思反思自己身上的问题。

这篇文章并非笔者原创,而是来自QGIS Planet的一篇博客,偶然看到这篇文章,觉得可能对大家有帮助,于是就翻译和整理出来了。
虽然以下内容主要是PyQGIS脚本,但是Python和C++的QGIS接口是基本一致的,因此使用C++的同学相信也会有所收获。

查询要素时,通常会使用到矢量图层的 QgsFeatureRequest对象,使用方法如下:

request = QgsFeatureRequest()
for feature in vector_layer.getFeatures(request)
# do something
  •  

这段代码会遍历整个矢量图层中所有的要素。而查询满足某个条件的矢量要素,则是通过设置QgsFeatureRequest对象来完成的,比如说:

request = QgsFeatureRequest().setFilterFid(1001)
feature_1001 = next(vector_layer.getFeatures(rrequest))
  •  

这样,getFeatures(request)方法就仅仅返回FID为1001的要素,而不是返回所有结果。

现在,这里有个陷阱:“调用getFeatures方法是代价是很大的”。对一个矢量图层调用getFeatures方法时,QGIS会与这个矢量图层的数据源建立一个连接,并创建一个筛选条件来选择所需要的要素,最后对数据源返回的结果进行解析。这个过程是非常耗时的,尤其是当矢量图层的数据源不在本地磁盘,而是在远程数据库中时。

减少调用getFeatures的次数

在PyQGIS中一个很常见的操作,就是传入一个ID列表,根据这个列表来从矢量图层中选取要素。这段操作如下代码所示:

for id in some_list_of_feature_ids:
    request = QgsFeatureRequest().setFilterFid(id)
    feature = next(vector_layer.getFeatures(request))
    # do something with the feature
  •  

注意,这里有一个非常大的陷阱。
上面我们提到过,getFeatures() 方法需要做很多操作(连接数据源、传入查询对象、接收返回数据、解析数据从中得到指定要素)才能返回到你能使用的feature。而上面这段代码在每一个id迭代的时候,都调用了一次getFeatures()方法。这就是为何当你的数据源比较大、而且包含了大量要素、或者数据源在远程数据库中的时候,查询要素循环的速度异常缓慢。

把上面的代码改写一下:

request = QgsFeatureRequest().setFilterFids(some_list_of_feature_ids)
for feature in vector_layer.getFeatures(request):
    # do something with the feature
  •  

现在,把QgsFeatureRequest对象事先用id列表构造好,这样整个循环就只调用了一次getFeatures()方法。虽然看起来好像这个代码改变不大,但是改写之后代码的效率跟之前可是大有提高,并且数据中的要素越多,这种效率的提高越是明显。

正确使用 QgsFeatureRequest 过滤器

还有一种通常的选择要素的代码可能是以下这样:

for feature in vector_layer.getFeatures():
    if not feature.id() in vector_layer.selectedFeatureIds():
        continue

    # do something with the feature
  •  

有什么问题?
注意到没有,这里会遍历这个矢量图层中所有的要素,然后再判断是否与我们选择的要素id一致,最后才针对这个要素做操作。如果你有10万个要素在这个图层中,你想象一下这个遍历的速度。。。

这个问题其实可以用 setFilterFids() 方法解决:

request = QgsFeatureRequest().setFilterFids(vector_layer.selectedFeatureIds())
for feature in vector_layer.getFeatures(request):
    # do something with feature
  •  

这样,QGIS会先选取满足条件的要素,然后仅仅遍历这一部分要素,程序就会高效很多。

或者,也有人喜欢使用QgsExpression对象来构造选择要素的条件,例如:

filter_expression = QgsExpression('my_field > 20')
for feature in vector_layer.getFeatures():
    if not filter_expression.evaluate(feature):
        continue

    # do something with the feature
  •  

同样的,上面的代码会遍历所有的要素,直到它找出所有‘my_field > 20’的集合,速度很慢,正确的方式应该是:

request = QgsFeatureRequest().setFilterExpression('my_field > 20')
for feature in vector_layer.getFeatures(request):
    # do something with the feature
  •  

QGIS会完成与特定数据源格式的查询交互问题,你所需要知道的就是这个表达式会按照它被设计的目的起作用,其他的你不需要关心。

仅仅请求你需要的值

这个问题通常是请求数据源返回了过多不需要的字段值,看下代码:

my_sum = 0
for feature in vector_layer.getFeatures(request):
    my_sum += feature['value']
  •  

实际上这里仅仅是用了‘value’这个字段的值,然而去返回了请求了整个要素的所有字段列表值。这里的循环上面已经用了我们上面提到的优化方法,遍历速度上没有办法再优化了,但是在这个请求数据的方面,还是可以再提高一下效率的。假设我们现在每个要素有100个字段,那么上面的代码就会返回这100个字段对应的所有值,而我们其实只使用了这100个字段中的1个名为‘value’的字段值。我们的请求会让QGIS去寻找所有的字段值,并且还有几何对象需要解析(也就是QgsGeometry),这时我们遍历的数据源中的要素数量很大的话,耗时也是非常大的。
解决方法就是“不要请求不需要的数据”。我们可以通过QgsFeatureRequest对象的setFlags(QgsFeatureRequest.NoGeometry)方法来实现不请求几何对象,用setSubsetOfAttributes()方法来告诉QGIS我们需要的字段值。

my_sum = 0
request = QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry).setSubsetOfAttributes(['value'], vector_layer.fields() )
for feature in vector_layer.getFeatures(request):
    my_sum += feature['value']
  •  

这样经过优化之后,这段代码不会请求我们用不着的要素的几何对象,也不会请求多余的字段值,自然就是大大提高了遍历的效率。

总结

写循环,要小心!

© 版权声明
THE END
喜欢就支持一下吧
点赞744 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容