QGis二次开发基础 — 根据属性查询要素

属性查询是GIS应用不可缺少的重要功能,尤其是在各种业务系统中,根据用户输入相应的查询条件,从属性要素中快速定位到用户感兴趣的要素,为业务应用提供了便利。本文就来聊一聊QGis二次开发中如何实现属性查询功能。

其实这个功能我在写属性表格功能的实现时就提到过相应的参考源码了,只不过当时没有给出具体的实现方案。

功能简介

还是简单描述一下功能的使用。
首先来看看通过属性来筛选属性表,打开属性表,见下图
这里写图片描述

在左下角分别点击 “Show All Features In Initial Canvas Extent” – “Column Filter”就能看到几个字段列表了。

选择要查询的一个字段,并输入这个字段的可查询的属性信息,如下图。
这里写图片描述

回车之后,属性表会变成下图这样。
这里写图片描述
这时候属性表已经查询到“cat”字段为“4”的要素。

留意到左下角的按钮变成了“Advanced Filter (Expression)”,点击这个按钮,就会打开表达式编辑器,也就是下图中的这个窗口。
这里写图片描述
这里输入相应的查询语句就行了。

源码分析

要实现这个功能,首先还是定位到QGis的属性表功能的源代码上来。
打开 qgsattributetabledialog.cpp 这个文件,找到 filterQueryChanged() 这个方法,代码如下

void QgsAttributeTableDialog::filterQueryChanged( const QString& query )
{
    QString str; // 这个是最后完整的查询字符串
    if ( mFilterButton->defaultAction() == mActionAdvancedFilter )
    {
        str = query;
    }
    else
    {
        QString fieldName = mFilterButton->defaultAction()->text();

        const QgsFields& flds = mLayer->pendingFields();
        int fldIndex = mLayer->fieldNameIndex( fieldName );
        if ( fldIndex < 0 )
        {
            return;
        }

        // 判断属性字段是否为数字
        QVariant::Type fldType = flds[fldIndex].type();
        bool numeric = ( fldType == QVariant::Int || fldType == QVariant::Double || fldType == QVariant::LongLong );

        // 如果属性是字符串,判断应该用“ILIKE”或者“LIKE”
        QString sensString = "ILIKE";
        if ( mCbxCaseSensitive->isChecked() )
        {
            sensString = "LIKE";
        }

        QSettings settings;
        QString nullValue = settings.value( "qgis/nullValue", "NULL" ).toString();

        if ( mFilterQuery->displayText() == nullValue )
        {
            str = QString( "%1 IS NULL" ).arg( QgsExpression::quotedColumnRef( fieldName ) );
        }
        else
        {
            str = QString( "%1 %2 '%3'" )
                  .arg( QgsExpression::quotedColumnRef( fieldName ) )
                  .arg( numeric ? "=" : sensString )
                  .arg( numeric
                        ? mFilterQuery->displayText().replace( "'", "''" )
                        :
                        "%" + mFilterQuery->displayText().replace( "'", "''" ) + "%" ); // escape quotes
        }
    }

    // 以上为解析字符串, str才是最后的查询字符串
    setFilterExpression( str );
    updateTitle(); // 更新属性窗口标题
}

然后跟踪到 setFilterExpression() 这个方法

void QgsAttributeTableDialog::setFilterExpression( QString filterString )
{
  mFilterQuery->setText( filterString );
  mFilterButton->setDefaultAction( mActionAdvancedFilter );
  mFilterButton->setPopupMode( QToolButton::MenuButtonPopup );
  mCbxCaseSensitive->setVisible( false );
  mFilterQuery->setVisible( true );
  mApplyFilterButton->setVisible( true );
  mMainView->setFilterMode( QgsAttributeTableFilterModel::ShowFilteredList );

  QgsFeatureIds filteredFeatures;
  QgsDistanceArea myDa;

  myDa.setSourceCrs( mLayer->crs().srsid() );
  myDa.setEllipsoidalMode( QgisApp::instance()->mapCanvas()->mapSettings().hasCrsTransformEnabled() );
  myDa.setEllipsoid( QgsProject::instance()->readEntry( "Measure", "/Ellipsoid", GEO_NONE ) );

  // 构造查询表达式
  QgsExpression filterExpression( filterString );
  // 检查有没有错误
  if ( filterExpression.hasParserError() )
  {
    QgisApp::instance()->messageBar()->pushMessage( tr( "Parsing error" ), filterExpression.parserErrorString(), QgsMessageBar::WARNING, QgisApp::instance()->messageTimeout() );
    return;
  }
  // 继续检查
  if ( ! filterExpression.prepare( mLayer->pendingFields() ) )
  {
    QgisApp::instance()->messageBar()->pushMessage( tr( "Evaluation error" ), filterExpression.evalErrorString(), QgsMessageBar::WARNING, QgisApp::instance()->messageTimeout() );
  }

  bool fetchGeom = filterExpression.needsGeometry();

  QApplication::setOverrideCursor( Qt::WaitCursor );
  // 重写鼠标指针形状之后,以下就开始查询了
  filterExpression.setGeomCalculator( myDa );
  QgsFeatureRequest request( mMainView->masterModel()->request() );
  request.setSubsetOfAttributes( filterExpression.referencedColumns(), mLayer->pendingFields() );
  if ( !fetchGeom )
  {
    request.setFlags( QgsFeatureRequest::NoGeometry );
  }
  QgsFeatureIterator featIt = mLayer->getFeatures( request );

  QgsFeature f;

  while ( featIt.nextFeature( f ) )
  {
    if ( filterExpression.evaluate( &f ).toInt() != 0 )
      filteredFeatures << f.id();

    // check if there were errors during evaluating
    if ( filterExpression.hasEvalError() )
      break;
  }

  featIt.close();

  mMainView->setFilteredFeatures( filteredFeatures );

  QApplication::restoreOverrideCursor();

  if ( filterExpression.hasEvalError() )
  {
    QgisApp::instance()->messageBar()->pushMessage( tr( "Error filtering" ), filterExpression.evalErrorString(), QgsMessageBar::WARNING, QgisApp::instance()->messageTimeout() );
    return;
  }
  mMainView->setFilterMode( QgsAttributeTableFilterModel::ShowFilteredList );
}

也就是说,你只要构建了一个查询字符串,传入这个方法,就能实现查询了。很简单吧。上一次的属性表格功能的实现中,我讲到把QGis的属性表格直接引入我们的工程中使用的方法。为它添加这个属性表查询的功能,就直接复制代码,改一下就可以了,没有比这更容易的了。

查询表达式编辑器

单独说一下这个东西,因为如果直接使用QGis的这个窗口,还需要一点配置才能运行。

在仿造了QGis属性表格之后,使用表达式编辑器实际上就是为了更方便的构建那个查询表达式。同样,讲讲快速实现,直接使用QGis表达式编辑器的方法。

首先,在QGis源码中找到 “qgsexpressionbuilder.ui”这个文件,在 “src/ui”目录下。将这个文件复制到我们的工程中,并加载进解决方案。右键编译一下,然后加入实现以下代码展示的slot函数,并绑定到相应的触发按钮上。

void qgis_devattrtableDialog::filterExpressionBuilder()
{
    // Show expression builder
    QgsExpressionBuilderDialog dlg( mLayer, mFilterQuery->text(), this );
    dlg.setWindowTitle( tr( "Expression based filter" ) );

    QgsDistanceArea myDa;
    myDa.setSourceCrs( mLayer->crs().srsid() );
    myDa.setEllipsoidalMode( qgis_dev::instance()->mapCanvas()->mapSettings().hasCrsTransformEnabled() );
    //myDa.setEllipsoid( QgsProject::instance()->readEntry( "Measure", "/Ellipsoid", GEO_NONE ) );
    dlg.setGeomCalculator( myDa );

    if ( dlg.exec() == QDialog::Accepted )
    {
        setFilterExpression( dlg.expressionText() );
    }
}
  •  

这样看起来就好了,但是运行会报错,说“找不到QSci/qscintilla.h” 这个文件。实际上,这是我们的Qt的一个插件,Qt默认是不会安装的。关于这个插件的作用,大家自己搜索一下。

来手动安装这个插件,只需要从 这里 下载,解压后,打开 Qt 的 Command Prompt

这里写图片描述

输入以下命令,定位到文件目录
这里写图片描述

然后分别运行

qmake qscintilla.pro
nmake
nmake install

 

其中 nmake 是在VS2010中,目录是”C:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\bin\\“,可以将其加入PATH环境变量。

安装好了以后,会发现Qt的安装目录下面就有了 QSci 的文件夹。

最后再来运行代码就不会有错误了。

最后

本文里面的代码都是尽可能直接使用QGis的源代码来实现功能,但是在业务系统里面,我们应该将功能进行相应的改造。比如一般的用户都不会希望查询的时候跳出一个输入表达式的对话框,会让用户觉得需要写代码,而他不会写也不想写,所以需要用各种条件输入的方式来让用户选择,最后在后台处理时在自己生成查询表达式就好了。

其实看我博客的朋友应该会发现,我写的功能都不是很难实现。对很多人来说难的地方可能就是看QGis源代码,并进行分析。这个没什么捷径,需要多思考,多想想这些代码下面隐藏的关系,当然,耐心是必需的。如果你都没看过源代码,QGis的二次开发就变得异常难了,因为它没有像Arcgis二次开发那么多的教程。当然这也是我正在做的事情,我希望尽我的努力,趁着我的兴趣,多创造一些入门的基础文档。

有人问我为什么要用QGis做二次开发,Arcgis不是方便很多么,功能也都很完备,教程也丰富,做起来多快啊?这个问题我也不知道怎么回答,现在国内GIS行业使用QGis软件的人都少,更别说二次开发了。

但我刚开始接触QGis的时候,就被它的简洁、优雅、方便打动了,这是Arcgis二次开发之后,突然发现原来可以这么优雅,不用那么庞大的十几个G的库文件。开发的时候只需要简单配置一下,源代码详尽,我可以自由的定制功能。但是说实话,现在还有很多关键技术没有解决,很多技术还不够熟悉,不够成熟,我对行业内部的很多业务流程也不熟悉,这是我没法现在用QGis自己开发什么有创新性的软件的原因。

我不知道我还能坚持学QGis多久,如果工作的琐事太多可能会占据很多时间,而我目前做QGis确实没有给我带来任何看得见的工作上的帮助。但这是我目前的兴趣,我只想趁着兴趣多做一会,免得以后没机会了。

与所有QGis开发者共勉!

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

昵称

取消
昵称表情代码图片

    暂无评论内容