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 );
    }
}

這個代碼的效果如下所示:

這裏寫圖片描述

這裏寫圖片描述

這裏寫圖片描述

最後

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

最後,感謝閱讀。

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