矢量编辑的功能,是让GIS软件成为生产力工具所必备的基础功能。本文想跟大家探讨一下QGis二次开发中的添加矢量要素功能。
文章的示例工程地址在 https://github.com/Jacory/qgis_dev, 可fork自己的版本,并留意我不定时的更新
注意:本文开头部分代码比较多,篇幅比较长。虽然并非所有东西都与本文直接相关,但是我想通过前面的介绍,让大家对工具的功能实现有个基本的了解,这样自己扩展功能的时候才有明确的思路。
注意2: 本文目前还并没有完全写完,后文的UML图显示也不完整,按理说不应该直接发布出来。一来是最近实在比较忙,没有完整的时间来整理,二来我希望读到的朋友能够对我的文章结构以及叙述方式上给一些意见与建议,也便于我边写边修改,我认为这样对一篇长文的最终形成是非常有益的。

QgsMapTool 这个类定义为所有地图工具的抽象父类,因此,所有与地图交互操作的工具都应该继承自这个类。来看一下 QgsMapTool 类的定义,如下
class GUI_EXPORT QgsMapTool : public QObject
{
Q_OBJECT
public:
//! 虚析构函数
virtual ~QgsMapTool();
virtual void canvasMoveEvent( QMouseEvent * e );
virtual void canvasDoubleClickEvent( QMouseEvent * e );
virtual void canvasPressEvent( QMouseEvent * e );
virtual void canvasReleaseEvent( QMouseEvent * e );
virtual void wheelEvent( QWheelEvent* e );
virtual void keyPressEvent( QKeyEvent* e );
virtual void keyReleaseEvent( QKeyEvent* e );
#ifdef HAVE_TOUCH
virtual bool gestureEvent( QGestureEvent* e );
#endif
Q_DECL_DEPRECATED virtual void renderComplete();
/** 这个方法用来给地图工具挂上一个action,通过action来指定这个地图工具应该完成的操作。
void setAction( QAction* action );
/** 返回指定的action,没有则返回null */
QAction* action();
/** 给这个地图工具挂接上一个按钮对象*/
void setButton( QAbstractButton* button );
/** 返回指定的button,没有则返回null */
QAbstractButton* button();
/** 设置用户指定的鼠标形态 */
virtual void setCursor( QCursor cursor );
/** 判断这个地图工具是否是完成缩放或漫游的操作。如果是,就完成相应的缩放或漫游,并把工具切换到上一个工具状态。 */
virtual bool isTransient();
/** 判断这个工具是否具备编辑功能。如果是,当地图不处于编辑状态时,它将不可访问。*/
virtual bool isEditTool();
virtual void activate();
virtual void deactivate();
QgsMapCanvas* canvas();
QString toolName() { return mToolName; }
/** 这个是2.3版本后添加的,只有identify tool等工具才使用,其他暂时不需要关注。*/
static double searchRadiusMM();
/** 这个也是2.3版本后添加的,只有identify tool等工具才使用,其他暂时不需要关注。 */
static double searchRadiusMU( const QgsRenderContext& context );
/** 同样是2.3版本后添加的,只有identify tool等工具才使用,其他暂时不需要关注。 */
static double searchRadiusMU( QgsMapCanvas * canvas );
signals:
void messageEmitted( QString message, QgsMessageBar::MessageLevel = QgsMessageBar::INFO );
void messageDiscarded();
void activated();
void deactivated();
private slots:
//! 用于当action被销毁时清除指针
void actionDestroyed();
protected:
//! 构造函数,传入地图画布指针作为参数
QgsMapTool( QgsMapCanvas* canvas );
QgsPoint toMapCoordinates( const QPoint& point );
QgsPoint toLayerCoordinates( QgsMapLayer* layer, const QPoint& point );
QgsPoint toLayerCoordinates( QgsMapLayer* layer, const QgsPoint& point );
QgsPoint toMapCoordinates( QgsMapLayer* layer, const QgsPoint& point );
QgsRectangle toLayerCoordinates( QgsMapLayer* layer, const QgsRectangle& rect );
QPoint toCanvasCoordinates( const QgsPoint& point );
QgsMapCanvas* mCanvas;
QCursor mCursor;
QAction* mAction;
QAbstractButton* mButton;
QString mToolName;
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
可以看到,在 QgsMapTool 类中主要定义的是鼠标\键盘操作的事件函数、地图工具的功能属性等。回想任意一个常用的地图工具,与地图做交互的主要是鼠标\键盘事件,然后这个工具可能还会有自己的鼠标指针图案、具有相应的名称、会有不用的切换状态,当然,与地图做交互自然要有各种地图坐标、屏幕坐标等等的转换功能。
同样,矢量图层的编辑工具也是一个地图工具,因此,以上这些属性它都具有。
再来看看 QgsMapToolAdvancedDigitizing 这个类。这个类继承自 QgsMapTool 类,并直接实现了它定义的事件响应。首先一个 QgsMapTool 的事件消息被捕获后,它的类型 QMouseEvent 会被转换为 QgsMapMouseEvent,这个事件消息会带上地图坐标信息,并且传递到 QgsMapTool 类的子类实例中。对应的子类通过实现 QgsMapTool 类的虚方法,来实现自己对事件处理的对应功能。而 QgsMapToolAdvancedDigitizing 其实是扮演一个地图数字化工具事件的响应者父类,为什么这么说?因为它也接收到消息以后也并没有直接定义响应方法,而是通过一个叫 QgsMapToolMapEventFilter 的类将地图事件过滤并重新封装,之后再传递给 QgsMapToolAdvancedDigitizing 的子类来实现。
来看看他的定义代码:
class APP_EXPORT QgsMapToolAdvancedDigitizing : public QgsMapTool
{
Q_OBJECT
public:
enum CaptureMode // 矢量化类型
{
CaptureNone,
CapturePoint,
CaptureLine,
CapturePolygon
};
explicit QgsMapToolAdvancedDigitizing( QgsMapCanvas* canvas );
~QgsMapToolAdvancedDigitizing();
void canvasPressEvent( QMouseEvent* e ) override;
void canvasReleaseEvent( QMouseEvent* e ) override;
void canvasMoveEvent( QMouseEvent* e ) override;
void canvasDoubleClickEvent( QMouseEvent* e ) override;
void keyPressEvent( QKeyEvent* event ) override;
void keyReleaseEvent( QKeyEvent* event ) override;
virtual void canvasMapPressEvent( QgsMapMouseEvent* e );
virtual void canvasMapReleaseEvent( QgsMapMouseEvent* e );
virtual void canvasMapMoveEvent( QgsMapMouseEvent* e );
virtual void canvasMapDoubleClickEvent( QgsMapMouseEvent* e );
virtual void canvasKeyPressEvent( QKeyEvent* e );
virtual void canvasKeyReleaseEvent( QKeyEvent* e );
bool cadAllowed() { return mCadAllowed; }
CaptureMode mode() { return mCaptureMode; }
protected:
//! 这个 dock widget 是用来装高级矢量化工具命令的
QgsAdvancedDigitizingDockWidget* mCadDockWidget;
bool mCadAllowed;
CaptureMode mCaptureMode;
bool mSnapOnPress;
bool mSnapOnRelease;
bool mSnapOnMove;
bool mSnapOnDoubleClick;
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
看了代码,我们可以看到,这个类也是一个抽象类,那么到底实现矢量图层编辑的功能代码在哪里呢?我们往下看。
这个类继承自 QgsMapToolAdvancedDigitizing ,是编辑矢量图层地图工具的父类。这次直接看代码:
class APP_EXPORT QgsMapToolEdit: public QgsMapToolAdvancedDigitizing
{
public:
//! 构造函数,接收 QgsMapCanvas 指针作为输入
QgsMapToolEdit( QgsMapCanvas* canvas );
virtual ~QgsMapToolEdit();
virtual bool isEditTool() override { return true; }
protected:
// 新建一个 QgsRubberBand 图层,并制定它的颜色、线宽等属性
// alternativeBand 如果设为true,会显示更多的样式,
// 如透明度、线样式等,默认为false。
QgsRubberBand* createRubberBand( QGis::GeometryType geometryType = QGis::Line, bool alternativeBand = false );
/**返回地图控件中的当前图层,没有则返回0*/
QgsVectorLayer* currentVectorLayer();
/**给矢量要素添加节点,并保证拓扑关系正确。
@param geom list of points (in layer coordinate system)
@return 0 in case of success*/
int addTopologicalPoints( const QList<QgsPoint>& geom );
/**通过信息栏提示当前图层不是矢量图层 */
void notifyNotVectorLayer();
/**通过信息栏提示当前图层为不可编辑状态 */
void notifyNotEditableLayer();
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
class APP_EXPORT QgsMapToolCapture : public QgsMapToolEdit
{
Q_OBJECT
public:
//! 构造函数,需要 QgsMapCanvas 指针,可以配置矢量化类型
QgsMapToolCapture( QgsMapCanvas* canvas, CaptureMode mode = CaptureNone );
virtual ~QgsMapToolCapture();
virtual void canvasMapMoveEvent( QgsMapMouseEvent* e ) override;
virtual void canvasMapPressEvent( QgsMapMouseEvent * e ) override;
virtual void canvasKeyPressEvent( QKeyEvent* e ) override;
virtual void deactivate() override;
public slots:
//! 当前图层改变时触发这个函数
void currentLayerChanged( QgsMapLayer *layer );
void addError( QgsGeometry::Error );
void validationFinished();
protected:
//! 返回下一个点的索引
int nextPoint( const QgsPoint& mapPoint, QgsPoint& layerPoint );
/** 添加一个地图坐标点到临时图层和矢量化列表,
成功则返回0,当前图层不是矢量图层则返回1,坐标转换失败则返回2*/
int addVertex( const QgsPoint& point );
/**撤销上次添加点*/
void undo();
void startCapturing();
bool isCapturing() const;
void stopCapturing();
void deleteTempRubberBand();
int size() { return mCaptureList.size(); }
QList<QgsPoint>::iterator begin() { return mCaptureList.begin(); }
QList<QgsPoint>::iterator end() { return mCaptureList.end(); }
const QList<QgsPoint> &points() { return mCaptureList; }
void setPoints( const QList<QgsPoint>& pointList ) { mCaptureList = pointList; }
void closePolygon();
private:
bool mCapturing;
/** 为线和多边形要素提供的临时图层*/
QgsRubberBand* mRubberBand;
/** 为线和多边形要素提供的,添加了最后一个鼠标点位置的临时矢量图层 */
QgsRubberBand* mTempRubberBand;
/** 用于存放线和多边形要素的捕获节点列表*/
QList<QgsPoint> mCaptureList;
void validateGeometry();
QString mTip;
QgsGeometryValidator *mValidator;
QList< QgsGeometry::Error > mGeomErrors;
QList< QgsVertexMarker * > mGeomErrorMarkers;
bool mCaptureModeFromLayer;
QgsVertexMarker* mSnappingMarker;
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
好了,接下来终于到正式添加要素的工具类了。QgsMapToolAddFeature 类继承自 QgsMapToolCapture 类,作用是添加一个新的点/线/多边形要素到一个已有矢量图层中。
来看定义代码:
class APP_EXPORT QgsMapToolAddFeature : public QgsMapToolCapture
{
Q_OBJECT
public:
//! 构造函数,接收 QgsMapCanvas 指针作为输入
QgsMapToolAddFeature( QgsMapCanvas* canvas );
virtual ~QgsMapToolAddFeature();
void canvasMapReleaseEvent( QgsMapMouseEvent * e ) override;
/** 添加要素函数
传入 QgsVectorLayer 指针、当前要素,以及是否实时显示 */
bool addFeature( QgsVectorLayer *vlayer, QgsFeature *f, bool showModal = true );
void activate() override;
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
小结
OK,我们来捋一捋。QgsMapTool 类定义了所有地图工具与用户交互的鼠标事件、键盘事件等,并包括了一些基本的通用方法。QgsMapToolAdvancedDigitizing 类继承自 QgsMapTool 类,并提供用于进行矢量化所需要的方法。QgsMapToolEdit 类又继承自 QgsMapToolAdvancedDigitizing 类,进一步定义了用于编辑图层所需的方法。最后 QgsMapToolAddFeature 类继承自 QgsMapToolEdit,并实现本文所关注的添加要素的方法,主要是在重写鼠标释放事件的函数中实现添加要素功能。
然后,我们通过一个时序图,来看一下添加一个矢量要素时,究竟发生了什么。

矢量图层添加要素功能实现
通过上文中对源码的剖析,现在要实现矢量图层添加要素的功能就有思路了。主要有两种办法:
- 自己定义一个类,继承自 QgsMapTool 并重写鼠标事件,在重写函数中,加入添加要素功能。
- 仿造 QGis 的模式,分别拷贝以上几个类,以及它们调用的其他类,来实现添加要素功能。
在以前的博客中,上面所讲的第二种方式都较为简单,但是本文的功能,要将所有相关类都拷贝过来,并理清它们之间的各种调用关系会稍微繁琐一点。如果你的关注点只是添加要素功能,其他编辑功能暂时不会使用,那么采用第一种方式是最直接,也是最简单的。但是如果你今后还需要添加其他的编辑功能,那我建议还是做第二种方法,毕竟以后的功能实现会更有章法,更轻松一点。
下面将会分别讲解第一种和第二种方法的具体实现方式,并提供示例代码,供大家使用。
第一种方法
在上文的源码剖析中,我们注意到,添加要素功能需要重写 QgsMapTool 的鼠标释放事件。那么我们需要做的就是定义一个类,直接继承自 QgsMapTool 类,并重写它的鼠标释放事件,来获取用户点击在地图画布上的位置。
class qgis_dev_addFeatureTool : public QgsMapTool
{
Q_OBJECT
public:
qgis_dev_addFeatureTool( QgsMapCanvas* mapCanvas );
~qgis_dev_addFeatureTool();
void canvasReleaseEvent( QMouseEvent* e ) override;
};
当然,我们需要在主界面触发这个工具类,我们在菜单条上新建了一个工具,叫 Add Feature,如下图所示

然后我们还需要在代码中绑定上这个工具的触发事件
void qgis_dev::on_actionAdd_Feature_triggered()
{
QgsMapTool* addFeatureTool = new qgis_dev_addFeatureTool( m_mapCanvas );
m_mapCanvas->setMapTool( addFeatureTool );
}
好了,现在开始实现工具的功能。我们知道,添加矢量要素,实际上调用的是 QgsVectorLayer 类的 addFeature() 方法。我们现在不考虑任何可能的bug与设计模式,仅仅考虑添加一个自定义点到这个矢量图层上,那么我们的代码就写成:
void qgis_dev_addFeatureTool::canvasReleaseEvent( QMouseEvent* e )
{
QgsVectorLayer* layer = qobject_cast<QgsVectorLayer*>( mCanvas->currentLayer() );
if( !layer ) {emit messageEmitted( tr( "not a valid vector layer." ) ); return;}
if( !layer->isEditable() ) {emit messageEmitted( tr( "can't edit this layer." ) ); return;}
QgsPoint savePoint = toLayerCoordinates( layer, mCanvas->mapSettings().mapToPixel().toMapCoordinates( e->pos() ) );
switch( layer->geometryType() )
{
case QGis::Point:
m_captureMode = CapturePoint;
break;
case QGis::Line:
m_captureMode = CaptureLine;
break;
case QGis::Polygon:
m_captureMode = CapturePolygon;
break;
default:
break;
}
QgsGeometry* g = 0;
if ( m_captureMode == CapturePoint )
{
if ( layer->wkbType() == QGis::WKBPoint || layer->wkbType() == QGis::WKBPoint25D )
{
g = QgsGeometry::fromPoint( savePoint );
}
else if( layer->wkbType() == QGis::WKBMultiPoint || layer->wkbType() == QGis::WKBMultiPoint25D )
{
g = QgsGeometry::fromMultiPoint( QgsMultiPoint() << savePoint );
}
}
else if ( m_captureMode == CaptureLine )
{
}
else if ( m_captureMode == CapturePolygon )
{
}
QgsFeature feature( layer->pendingFields(), 0 );
feature.setGeometry( g );
layer->addFeature( feature, true );
mCanvas->setExtent( layer->extent() );
mCanvas->refresh();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
直接编译运行,并运用我们上一次讲到的矢量图层的建立功能,新建一个空的点矢量图层。然和我们就可以开始在这个图层上画点了。

留意到,上面的方法中,并没有实现线和多边形的添加。现在来讲解这两种类型。首先是线要素,添加一个线要素的步骤如下:
- 鼠标左键单击第一个点
- 鼠标左键单击第二个点
- 鼠标左键单击第三个点
- ……
- 鼠标右键完成
因此,想到要加入对鼠标按键的判断,并且还需要有一个数据结构来存储添加进来的这些点。在头文件里面定义这个存储结构为
QList<QgsPoint> mCaptureList;
现在来完成上面定义的那几个步骤
else if ( m_captureMode == CaptureLine )
{
if ( e->button() == Qt::LeftButton )
{
m_captureList.append( mCanvas->mapSettings().mapToPixel().toMapCoordinates( e->pos() ) );
}
else if ( e->button() == Qt::RightButton )
{
if ( m_captureList.size() < 2 ) { return; }
if ( layer->wkbType() == QGis::WKBLineString || layer->wkbType() == QGis::WKBLineString25D )
{
g = QgsGeometry::fromPolyline( m_captureList.toVector() );
}
else if ( layer->wkbType() == QGis::WKBMultiLineString || layer->wkbType() == QGis::WKBMultiLineString25D )
{
g = QgsGeometry::fromMultiPolyline( QgsMultiPolyline() << m_captureList.toVector() );
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
这样,新建一个线图层,然后打开添加要素工具,并随便用左键点三个点,最后右键点一下(因为没有rubber band,点击的时候看不到点的实时刷新)。最后,会看到如下图的效果。

最后一个是多边形的添加了,跟线图层其实挺像的。
if ( e->button() == Qt::LeftButton )
{
m_captureList.append( mCanvas->mapSettings().mapToPixel().toMapCoordinates( e->pos() ) );
}
else if ( e->button() == Qt::RightButton )
{
if ( m_captureList.size() < 3 ) { return; }
if ( layer->wkbType() == QGis::WKBPolygon || layer->wkbType() == QGis::WKBPolygon25D )
{
g = QgsGeometry::fromPolygon( QgsPolygon() << m_captureList.toVector() );
}
else if ( layer->wkbType() == QGis::WKBMultiPolygon || layer->wkbType() == QGis::WKBMultiPolygon25D )
{
g = QgsGeometry::fromMultiPolygon( QgsMultiPolygon() << ( QgsPolygon() << m_captureList.toVector() ) );
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
同样,新建一个多边形图层,然后随便点三个点,并以右键结束,会得到如下效果

至此,核心的功能就讲解完了。但是如果代码里面光是这样写的话bug就会很多,体验也不会好。因此我下面给出比较完整的示例代码供大家测试使用,虽然会感觉比上面的代码多了好多东西,但是核心是不变的,增加的代码只不过是为了完善而已。
首先是完整的 .h 文件
#ifndef QGIS_DEV_ADDFEATURETOOL_H
#define QGIS_DEV_ADDFEATURETOOL_H
#include <QObject>
#include <QMouseEvent>
#include <QList>
#include <qgsmaptool.h>
#include <qgsmapcanvas.h>
#include "qgsmapmouseevent.h"
#include <qgsvectorlayer.h>
#include "qgsfeature.h"
#include <qgsgeometryvalidator.h>
#include <qgsvertexmarker.h>
#include "qgsrubberband.h"
#include "qgis.h"
class qgis_dev_addFeatureTool : public QgsMapTool
{
Q_OBJECT
public:
qgis_dev_addFeatureTool( QgsMapCanvas* mapCanvas );
~qgis_dev_addFeatureTool();
enum CaptureMode
{
CaptureNone,
CapturePoint,
CaptureLine,
CapturePolygon
};
void canvasReleaseEvent( QMouseEvent* e ) override;
bool addFeature( QgsVectorLayer *vlayer, QgsFeature *f, bool showModal = true );
void activate() override;
CaptureMode mode();
int size() { return m_captureList.size(); }
const QList<QgsPoint> &points() { return m_captureList; }
private:
void notifyNotVectorLayer();
void notifyNotEditableLayer();
int addVertex( const QgsPoint& point );
int nextPoint( const QgsPoint& mapPoint, QgsPoint& layerPoint );
QgsRubberBand* createRubberBand( QGis::GeometryType geometryType = QGis::Line, bool alternativeBand = false );
void startCapturing();
void stopCapturing();
void deleteTempRubberBand();
QList<QgsPoint> m_captureList;
CaptureMode m_captureMode;
bool mCapturing;
QgsRubberBand* mRubberBand;
QgsRubberBand* mTempRubberBand;
QString mTip;
QgsGeometryValidator *mValidator;
QList< QgsGeometry::Error > mGeomErrors;
QList< QgsVertexMarker * > mGeomErrorMarkers;
bool mCaptureModeFromLayer;
void validateGeometry();
QgsVertexMarker* mSnappingMarker;
};
#endif // QGIS_DEV_ADDFEATURETOOL_H
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
然后是 .cpp 文件
#include "qgis_dev_addfeaturetool.h"
#include "qgis_dev.h"
#include <QStringList>
#include <qgsvectorlayer.h>
#include <qgslogger.h>
#include <qgsvectordataprovider.h>
#include "qgscsexception.h"
#include "qgsproject.h"
#include "qgsmaplayerregistry.h"
#include "qgsmaptopixel.h"
#include "qgsgeometry.h"
#include "qgsfeature.h"
qgis_dev_addFeatureTool::qgis_dev_addFeatureTool( QgsMapCanvas* mapCanvas )
: QgsMapTool( mapCanvas )
{
mToolName = tr( "Add Feature" );
mRubberBand = 0;
mTempRubberBand = 0;
mValidator = 0;
}
qgis_dev_addFeatureTool::~qgis_dev_addFeatureTool()
{
}
bool qgis_dev_addFeatureTool::addFeature( QgsVectorLayer *layer, QgsFeature *feature, bool showModal )
{
if ( !layer || !layer->isEditable() ) {return false;}
QgsAttributeMap defaultAttributes;
QgsVectorDataProvider *provider = layer->dataProvider();
QSettings settings;
bool reuseLastValues = settings.value( "/qgis/digitizing/reuseLastValues", false ).toBool();
QgsDebugMsg( QString( "reuseLastValues: %1" ).arg( reuseLastValues ) );
const QgsFields& fields = layer->pendingFields();
feature->initAttributes( fields.count() );
for ( int idx = 0; idx < fields.count(); ++idx )
{
QVariant v = provider->defaultValue( idx );
feature->setAttribute( idx, v );
}
bool isDisabledAttributeValuesDlg = ( fields.count() == 0 ) || settings.value( "/qgis/digitizing/disable_enter_attribute_values_dialog", false ).toBool();
switch ( layer->featureFormSuppress() )
{
case QgsVectorLayer::SuppressOn:
isDisabledAttributeValuesDlg = true;
break;
case QgsVectorLayer::SuppressOff:
isDisabledAttributeValuesDlg = false;
break;
case QgsVectorLayer::SuppressDefault:
break;
}
if ( isDisabledAttributeValuesDlg )
{
layer->beginEditCommand( "" );
bool mFeatureSaved = layer->addFeature( *feature );
if ( mFeatureSaved )
{
layer->endEditCommand();
}
else
{
layer->destroyEditCommand();
}
}
else
{
}
}
void qgis_dev_addFeatureTool::canvasReleaseEvent( QMouseEvent* e )
{
QgsVectorLayer* layer = qobject_cast<QgsVectorLayer*>( mCanvas->currentLayer() );
if( !layer ) {emit messageEmitted( tr( "not a valid vector layer." ) ); return;}
QgsVectorDataProvider* provider = layer->dataProvider();
if ( !( provider->capabilities() & QgsVectorDataProvider::AddFeatures ) )
{
emit messageEmitted(
tr( "The data provider for this layer does not support the addition of features." ),
QgsMessageBar::WARNING );
return;
}
if( !layer->isEditable() ) {emit messageEmitted( tr( "can't edit this layer." ) ); return;}
QgsPoint savePoint;
try
{
savePoint = toLayerCoordinates( layer, mCanvas->mapSettings().mapToPixel().toMapCoordinates( e->pos() ) );
QgsDebugMsg( "savePoint = " + savePoint.toString() );
}
catch ( QgsCsException &cse )
{
Q_UNUSED( cse );
emit messageEmitted( tr( "Cannot transform the point to the layers coordinate system" ), QgsMessageBar::WARNING );
return;
}
switch( layer->geometryType() )
{
case QGis::Point:
m_captureMode = CapturePoint;
break;
case QGis::Line:
m_captureMode = CaptureLine;
break;
case QGis::Polygon:
m_captureMode = CapturePolygon;
break;
default:
break;
}
QgsGeometry* g = 0;
if ( m_captureMode == CapturePoint )
{
if ( layer->wkbType() == QGis::WKBPoint || layer->wkbType() == QGis::WKBPoint25D )
{
g = QgsGeometry::fromPoint( savePoint );
}
else if( layer->wkbType() == QGis::WKBMultiPoint || layer->wkbType() == QGis::WKBMultiPoint25D )
{
g = QgsGeometry::fromMultiPoint( QgsMultiPoint() << savePoint );
}
QgsFeature feature( layer->pendingFields(), 0 );
feature.setGeometry( g );
addFeature( layer, &feature, false );
mCanvas->setExtent( layer->extent() );
mCanvas->refresh();
}
else if ( m_captureMode == CaptureLine || m_captureMode == CapturePolygon )
{
if ( e->button() == Qt::LeftButton )
{
int error = addVertex( mCanvas->mapSettings().mapToPixel().toMapCoordinates( e->pos() ) );
if ( error == 1 ) {return;}
else if ( error == 2 )
{
emit messageEmitted( tr( "Cannot transform the point to the layers coordinate system" ), QgsMessageBar::WARNING );
return;
}
startCapturing();
}
else if ( e->button() == Qt::RightButton )
{
deleteTempRubberBand();
if ( m_captureMode == CaptureLine && m_captureList.size() < 2 ) { return; }
if ( m_captureMode == CapturePolygon && m_captureList.size() < 3 ) { return; }
QgsFeature* feature = new QgsFeature( layer->pendingFields(), 0 );
QgsGeometry* g = 0;
if ( m_captureMode == CaptureLine )
{
if ( layer->wkbType() == QGis::WKBLineString || layer->wkbType() == QGis::WKBLineString25D )
{
g = QgsGeometry::fromPolyline( m_captureList.toVector() );
}
else if ( layer->wkbType() == QGis::WKBMultiLineString || layer->wkbType() == QGis::WKBMultiLineString25D )
{
g = QgsGeometry::fromMultiPolyline( QgsMultiPolyline() << m_captureList.toVector() );
}
else
{
emit messageEmitted( tr( "Cannot add feature. Unknown WKB type" ), QgsMessageBar::CRITICAL );
stopCapturing();
delete feature;
return;
}
feature->setGeometry( g );
}
else if ( m_captureMode == CapturePolygon )
{
if ( layer->wkbType() == QGis::WKBPolygon || layer->wkbType() == QGis::WKBPolygon25D )
{
g = QgsGeometry::fromPolygon( QgsPolygon() << m_captureList.toVector() );
}
else if ( layer->wkbType() == QGis::WKBMultiPolygon || layer->wkbType() == QGis::WKBMultiPolygon25D )
{
g = QgsGeometry::fromMultiPolygon( QgsMultiPolygon() << ( QgsPolygon() << m_captureList.toVector() ) );
}
else
{
emit messageEmitted( tr( "Cannot add feature. Unknown WKB type" ), QgsMessageBar::CRITICAL );
stopCapturing();
delete feature;
return;
}
if ( !g )
{
stopCapturing();
delete feature;
return;
}
feature->setGeometry( g );
int avoidIntersectionsReturn = feature->geometry()->avoidIntersections();
if ( avoidIntersectionsReturn == 1 )
{
}
#if 0
else if ( avoidIntersectionsReturn == 2 )
{
emit messageEmitted( tr( "The feature could not be added because removing the polygon intersections would change the geometry type" ), QgsMessageBar::CRITICAL );
delete feature;
stopCapturing();
return;
}
#endif
else if ( avoidIntersectionsReturn == 3 )
{
emit messageEmitted( tr( "An error was reported during intersection removal" ), QgsMessageBar::CRITICAL );
}
if ( !feature->geometry()->asWkb() )
{
QString reason;
if ( avoidIntersectionsReturn != 2 )
{
reason = tr( "The feature cannot be added because it's geometry is empty" );
}
else
{
reason = tr( "The feature cannot be added because it's geometry collapsed due to intersection avoidance" );
}
emit messageEmitted( reason, QgsMessageBar::CRITICAL );
delete feature;
stopCapturing();
return;
}
}
if ( addFeature( layer, feature, false ) )
{
int topologicalEditing = QgsProject::instance()->readNumEntry( "Digitizing", "/TopologicalEditing", 0 );
QStringList intersectionLayers = QgsProject::instance()->readListEntry( "Digitizing", "/AvoidIntersectionsList" );
bool avoidIntersection = !intersectionLayers.isEmpty();
if ( avoidIntersection )
{
QStringList::const_iterator lIt = intersectionLayers.constBegin();
for ( ; lIt != intersectionLayers.constEnd(); ++lIt )
{
QgsMapLayer* ml = QgsMapLayerRegistry::instance()->mapLayer( *lIt );
QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( ml );
if ( vl && vl->geometryType() == QGis::Polygon && vl->isEditable() )
{
vl->addTopologicalPoints( feature->geometry() );
}
}
}
else if ( topologicalEditing )
{
layer->addTopologicalPoints( feature->geometry() );
}
}
stopCapturing();
}
}
}
void qgis_dev_addFeatureTool::activate()
{
QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( mCanvas->currentLayer() );
if ( layer && layer->geometryType() == QGis::NoGeometry )
{
QgsFeature f;
addFeature( layer, &f, false );
return;
}
QgsMapTool::activate();
}
void qgis_dev_addFeatureTool::notifyNotVectorLayer()
{
emit messageEmitted( tr( "No active vector layer" ) );
}
void qgis_dev_addFeatureTool::notifyNotEditableLayer()
{
emit messageEmitted( tr( "Layer not editable" ) );
}
qgis_dev_addFeatureTool::CaptureMode qgis_dev_addFeatureTool::mode()
{
return m_captureMode;
}
int qgis_dev_addFeatureTool::addVertex( const QgsPoint& point )
{
if ( mode() == CaptureNone ) { QgsDebugMsg( "invalid capture mode" ); return 2;}
QgsPoint layerPoint;
int res = nextPoint( point, layerPoint );
if ( res != 0 ) {return res;}
if ( !mRubberBand )
{
mRubberBand = createRubberBand( m_captureMode == CapturePolygon ? QGis::Polygon : QGis::Line );
}
mRubberBand->addPoint( point );
m_captureList.append( layerPoint );
if ( !mTempRubberBand )
{
mTempRubberBand = createRubberBand( m_captureMode == CapturePolygon ? QGis::Polygon : QGis::Line, true );
}
else
{
mTempRubberBand->reset( m_captureMode == CapturePolygon ? true : false );
}
if ( m_captureMode == CaptureLine )
{
mTempRubberBand->addPoint( point );
}
else if ( m_captureMode == CapturePolygon )
{
const QgsPoint *firstPoint = mRubberBand->getPoint( 0, 0 );
mTempRubberBand->addPoint( *firstPoint );
mTempRubberBand->movePoint( point );
mTempRubberBand->addPoint( point );
}
validateGeometry();
return 0;
}
void qgis_dev_addFeatureTool::startCapturing()
{
mCapturing = true;
}
void qgis_dev_addFeatureTool::deleteTempRubberBand()
{
if ( mTempRubberBand )
{
delete mTempRubberBand;
mTempRubberBand = 0;
}
}
void qgis_dev_addFeatureTool::stopCapturing()
{
if ( mRubberBand )
{
delete mRubberBand;
mRubberBand = 0;
}
if ( mTempRubberBand )
{
delete mTempRubberBand;
mTempRubberBand = 0;
}
while ( !mGeomErrorMarkers.isEmpty() )
{
delete mGeomErrorMarkers.takeFirst();
}
mGeomErrors.clear();
mCapturing = false;
m_captureList.clear();
mCanvas->refresh();
}
int qgis_dev_addFeatureTool::nextPoint( const QgsPoint& mapPoint, QgsPoint& layerPoint )
{
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mCanvas->currentLayer() );
if ( !vlayer ) { QgsDebugMsg( "no vector layer" ); return 1;}
try
{
layerPoint = toLayerCoordinates( vlayer, mapPoint );
}
catch ( QgsCsException &cse )
{
Q_UNUSED( cse );
QgsDebugMsg( "transformation to layer coordinate failed" );
return 2;
}
return 0;
}
QgsRubberBand* qgis_dev_addFeatureTool::createRubberBand( QGis::GeometryType geometryType , bool alternativeBand )
{
QSettings settings;
QgsRubberBand* rb = new QgsRubberBand( mCanvas, geometryType );
rb->setWidth( settings.value( "/qgis/digitizing/line_width", 1 ).toInt() );
QColor color( settings.value( "/qgis/digitizing/line_color_red", 255 ).toInt(),
settings.value( "/qgis/digitizing/line_color_green", 0 ).toInt(),
settings.value( "/qgis/digitizing/line_color_blue", 0 ).toInt() );
double myAlpha = settings.value( "/qgis/digitizing/line_color_alpha", 200 ).toInt() / 255.0;
if ( alternativeBand )
{
myAlpha = myAlpha * settings.value( "/qgis/digitizing/line_color_alpha_scale", 0.75 ).toDouble();
rb->setLineStyle( Qt::DotLine );
}
if ( geometryType == QGis::Polygon )
{
color.setAlphaF( myAlpha );
}
color.setAlphaF( myAlpha );
rb->setColor( color );
rb->show();
return rb;
}
void qgis_dev_addFeatureTool::validateGeometry()
{
QSettings settings;
if ( settings.value( "/qgis/digitizing/validate_geometries", 1 ).toInt() == 0 ) {return;}
if ( mValidator )
{
mValidator->deleteLater();
mValidator = 0;
}
mTip = "";
mGeomErrors.clear();
while ( !mGeomErrorMarkers.isEmpty() )
{
delete mGeomErrorMarkers.takeFirst();
}
QgsGeometry *g = 0;
switch ( m_captureMode )
{
case CaptureNone:
case CapturePoint:
return;
case CaptureLine:
if ( m_captureList.size() < 2 ) {return;}
g = QgsGeometry::fromPolyline( m_captureList.toVector() );
break;
case CapturePolygon:
if ( m_captureList.size() < 3 ) {return;}
g = QgsGeometry::fromPolygon( QgsPolygon() << ( QgsPolyline() << m_captureList.toVector() << m_captureList[0] ) );
break;
}
if ( !g ) {return;}
mValidator = new QgsGeometryValidator( g );
connect( mValidator, SIGNAL( errorFound( QgsGeometry::Error ) ), this, SLOT( addError( QgsGeometry::Error ) ) );
connect( mValidator, SIGNAL( finished() ), this, SLOT( validationFinished() ) );
mValidator->start();
QStatusBar *sb = qgis_dev::instance()->statusBar();
sb->showMessage( tr( "Validation started." ) );
delete g;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
- 294
- 295
- 296
- 297
- 298
- 299
- 300
- 301
- 302
- 303
- 304
- 305
- 306
- 307
- 308
- 309
- 310
- 311
- 312
- 313
- 314
- 315
- 316
- 317
- 318
- 319
- 320
- 321
- 322
- 323
- 324
- 325
- 326
- 327
- 328
- 329
- 330
- 331
- 332
- 333
- 334
- 335
- 336
- 337
- 338
- 339
- 340
- 341
- 342
- 343
- 344
- 345
- 346
- 347
- 348
- 349
- 350
- 351
- 352
- 353
- 354
- 355
- 356
- 357
- 358
- 359
- 360
- 361
- 362
- 363
- 364
- 365
- 366
- 367
- 368
- 369
- 370
- 371
- 372
- 373
- 374
- 375
- 376
- 377
- 378
- 379
- 380
- 381
- 382
- 383
- 384
- 385
- 386
- 387
- 388
- 389
- 390
- 391
- 392
- 393
- 394
- 395
- 396
- 397
- 398
- 399
- 400
- 401
- 402
- 403
- 404
- 405
- 406
- 407
- 408
- 409
- 410
- 411
- 412
- 413
- 414
- 415
- 416
- 417
- 418
- 419
- 420
- 421
- 422
- 423
- 424
- 425
- 426
- 427
- 428
- 429
- 430
- 431
- 432
- 433
- 434
- 435
- 436
- 437
- 438
- 439
- 440
- 441
- 442
- 443
- 444
- 445
- 446
- 447
- 448
- 449
- 450
- 451
- 452
- 453
- 454
- 455
- 456
- 457
- 458
- 459
- 460
- 461
- 462
- 463
- 464
- 465
- 466
- 467
- 468
- 469
- 470
- 471
- 472
- 473
- 474
- 475
- 476
- 477
- 478
- 479
- 480
- 481
- 482
- 483
- 484
- 485
- 486
- 487
- 488
- 489
- 490
- 491
- 492
- 493
- 494
- 495
- 496
把上面的类文件复制,并根据实际情况做适当的依赖项修改,添加到主界面事件后,会得到下图的运行效果:

注意:这里还没有讲保存操作,因此目前的图层是没法保存的。
第二种方法
第二种方法,自然就是依葫芦画瓢,直接使用QGis现有的代码。先来看一个流程图:

未完待更~