属性识别工具,也就是常用的 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 );
}
}
这个代码的效果如下所示:
最后
这个功能并不复杂,但通过对这个功能的实现方法的探讨,可以帮助我们后期开发的时候更合理的做一些自己想要的功能扩展。例如,你可以接收到属性返回的结果,并根据要素中的某个特殊属性,显示相应的动画。还可以根据属性的值的大小,设计显示不同高度的柱状图,不同比例的柱状图等等。
最后,感谢阅读。
暂无评论内容