1. 程式人生 > >QGis二次開發基礎 -- 屬性識別工具的實現

QGis二次開發基礎 -- 屬性識別工具的實現

屬性識別工具,也就是常用的 identify 工具,它常常與諸如放大、縮小等地圖工具放在一起,提供瀏覽地圖要素的一項基本功能。為什麼要單獨討論一下這個工具,是因為它與普通的地圖瀏覽工具的實現有一些微小的差異。下面通過原始碼的學習,來了解這個工具的實現方法以及掌握屬性識別功能的實現機制。

這裡寫圖片描述

相關類

要實現一個功能,首先自然是找到這個功能相關的類,並檢視類之間的一些關係。這裡,屬性識別也是地圖工具,因此,先檢視一下地圖工具類,也就是 QgsMapTool 類。QgsMapTool 類是一個抽象類,它的子類負責地圖瀏覽功能。

這裡寫圖片描述

通過上圖可以看到幾個基本的工具類

  • 漫遊工具 – QgsMapToolPan
  • 觸控工具(需要觸控式螢幕的支援) – QgsMapToolTouch
  • 縮放工具 – QgsMapToolZoom (放大和縮小僅僅是縮放係數不同來而已,因此縮放共用一個類)

這些工具的使用非常簡單,只需要初始化一個對應工具類的例項,並設定地圖畫布的 MapTool 為相應的例項就可以了,程式碼如下:

QgsMapToolPan* m_mapToolPan = new QgsMapToolPan( m_mapCanvas );
QgsMapToolZoom* m_mapToolZoomIn = new QgsMapToolZoom( m_mapCanvas, false );
QgsMapToolZoom*
m_mapToolZoomOut = new QgsMapToolZoom( m_mapCanvas, true ); m_mapCanvas->setMapTool( m_mapToolPan ); // 工具切換

還有一個 QgsMapToolEmitPoint 類,是用來發出地圖上使用者選點座標的,這個工具可以做一些自定義的地圖互動功能,在圖層檔案進行編輯的時候也可以使用。

除了上面說的這些,剩下兩個類 QgsMapToolIdentify 和它的派生類 QgsMapToolIdentifyFeature。這兩個類就是我們關注的重點了,看名字就知道這兩個類負責屬性的識別了。

QgsMapToolIdentify

這個類的用法與上面提到的工具有點微小的區別,如果像上面那樣設定工具,雖然切換成功後滑鼠圖示會變成識別工具的樣式,但是無論如何點選都不會有任何效果。這是因為,在這個類的實現程式碼中,滑鼠事件的實現部分是下面這樣的:

void QgsMapToolIdentify::canvasMoveEvent( QMouseEvent * e )
{
  Q_UNUSED( e );
}

void QgsMapToolIdentify::canvasPressEvent( QMouseEvent * e )
{
  Q_UNUSED( e );
}

void QgsMapToolIdentify::canvasReleaseEvent( QMouseEvent * e )
{
  Q_UNUSED( e );
}

並沒有實現程式碼,因此滑鼠事件是不被處理的。

再來關注定義在這個類中的一個結構體,它定義了屬性識別的返回結果。

struct IdentifyResult
{
  IdentifyResult() {}

  IdentifyResult( QgsMapLayer * layer, QgsFeature feature, QMap< QString, QString > derivedAttributes ):
      mLayer( layer ), mFeature( feature ), mDerivedAttributes( derivedAttributes ) {}

  IdentifyResult( QgsMapLayer * layer, QString label, QMap< QString, QString > attributes, QMap< QString, QString > derivedAttributes ):
      mLayer( layer ), mLabel( label ), mAttributes( attributes ), mDerivedAttributes( derivedAttributes ) {}

  IdentifyResult( QgsMapLayer * layer, QString label, QgsFields fields, QgsFeature feature, QMap< QString, QString > derivedAttributes ):
      mLayer( layer ), mLabel( label ), mFields( fields ), mFeature( feature ), mDerivedAttributes( derivedAttributes ) {}

  QgsMapLayer* mLayer; // 圖層
  QString mLabel; // 標籤 
  QgsFields mFields; // 屬性欄位
  QgsFeature mFeature; // 選中識別的要素
  QMap< QString, QString > mAttributes; // 屬性鍵值對
  QMap< QString, QString > mDerivedAttributes; // 派生屬性鍵值對
  QMap< QString, QVariant > mParams; // 引數鍵值對
};

主要關注幾個成員欄位, 它們定義了識別返回結果的形式。

同時這個類中還有很多方法是屬性識別功能的實現。看到這裡,我想思路就有了,要實現屬性識別的功能,需要繼承這個類,並重寫滑鼠事件的方法來處理識別的返回結果結構體就行了。

QgsMapToolIdentifyFeature

這個類專門為向量資料的屬性識別功能而設計,不能處理柵格圖層的屬性。實際上,這個類正是體現了上面提到的思路。它繼承自 QgsMapTool 類,並且從它的重寫的滑鼠事件實現程式碼中,可以看到它處理識別返回結果的方式。

void QgsMapToolIdentifyFeature::canvasReleaseEvent( QMouseEvent* e )
{

  QgsPoint point = mCanvas->getCoordinateTransform()->toMapCoordinates( e->x(), e->y() );

  QList<IdentifyResult> results;
  if ( !identifyVectorLayer( &results, mLayer, point ) )
    return;

  // TODO: display a menu when several features identified

  emit featureIdentified( results[0].mFeature );
  emit featureIdentified( results[0].mFeature.id() );
}

可以看到,它的處理事件發射了兩個訊號,分別將選中的要素和要素的id作為引數傳了出去。但是它並沒有直接處理。

如果要顯示一個類似QGis屬性識別的視窗,還需要我們自己定義一個類,然後通過 connect 的形式,將這裡發射的 signal 與相應的 slot 方法連線。

QgsMapToolIdentifyAction

這個類是QGis定義在qgis_app中定義的,並沒有直接提供在二次開發API中,原理同 QgsMapToolIdentifyFeature 大致相同,也是繼承自 QgsMapToolIdentify 類,並重寫滑鼠事件處理識別結果。

這個類在處理結果的時候呼叫了一個叫 QgsIdentifyResultsDialog 的類,這是QGis屬性識別視窗的定義。

若想直接使用 QgsIdentifyResultsDialog 這個視窗,需要將相應的 .ui 檔案以及視窗類檔案拷貝到自己的工程中,並呼叫視窗類的初始化以及顯示方法。

實現方法

講到這裡,我們要實現屬性識別功能,就有了兩種辦法了:

  • 定義方法接收 QgsMapToolIdentifyFeature 類發射的訊號,處理屬性識別返回的結果
  • 模仿 QgsMapToolIdentifyAction 類的做法,定義一個類繼承自 QgsMapToolIdentify, 並在重寫的滑鼠事件中處理屬性識別返回的結果

第一種方法只能處理向量圖層的識別,第二種方法可以自定義擴充套件柵格以及其他圖層的屬性識別。

由於兩種方法實際上在處理識別返回結果的地方原理是一樣的,因此下面只示例第二種方法的實現。

首先定義一個類,繼承自 QgsMapToolIdentify,並重寫滑鼠釋放的事件。

class qgis_devMapToolIdentifyAction : public QgsMapToolIdentify
{
    Q_OBJECT
public:
    qgis_devMapToolIdentifyAction( QgsMapCanvas * canvas );
    ~qgis_devMapToolIdentifyAction();

    //! 重寫滑鼠鍵釋放事件
    virtual void canvasReleaseEvent( QMouseEvent * e );
};

滑鼠釋放事件的實現程式碼如下。

void qgis_devMapToolIdentifyAction::canvasReleaseEvent( QMouseEvent * e )
{
    IdentifyMode mode = QgsMapToolIdentify::LayerSelection; // 控制識別模式
    QList<IdentifyResult> results = QgsMapToolIdentify::identify( e->x(), e->y(), mode ); // 這句返回識別結果

    if ( results.isEmpty() )
    {
        qgis_dev::instance()->statusBar()->showMessage( tr( "No features at this position found." ) );
    }
    else
    {
        // 顯示出識別結果,這裡僅作示例,結果的展示方式可以自定義,你也可以自己設計一個視窗,在這裡接收返回結果並顯示出來。
        IdentifyResult feature = results.at( 0 );
        QString title = feature.mLayer->name();
        QString content = feature.mFeature.attribute( 1 ).toString();
        // 顯示識別視窗
        QMessageBox::critical( NULL,
                               title,
                               content );
    }
}

這個程式碼的效果如下所示:

這裡寫圖片描述

這裡寫圖片描述

這裡寫圖片描述

最後

這個功能並不複雜,但通過對這個功能的實現方法的探討,可以幫助我們後期開發的時候更合理的做一些自己想要的功能擴充套件。例如,你可以接收到屬性返回的結果,並根據要素中的某個特殊屬性,顯示相應的動畫。還可以根據屬性的值的大小,設計顯示不同高度的柱狀圖,不同比例的柱狀圖等等。

最後,感謝閱讀。