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開發者共勉!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章