I want to customize the QGIS editing tool in the following way.

Scenario: The first and last point of the line layer must have a point layer.


  1. User selects a line layer.
  2. User starts/toggles the line editing.
  3. When user starts clicking on the map (first point) of line there should be a check that it must be snapped to the one of the point layer point loaded on the QGIS if not then message must be displayed that no point found at the start of the line.

I tried the following:

  • signal geometryChanged works on existing geometry
  • signal featureAdded works after the geometry and attribute is added
  • signal committedFeaturesAdded works after the edited geom and attribute is commmited
  • class qgis.gui.QgsMapToolEdit does not have signal for geometry add trigger
  • cannot implement multiple MapTool as one canvas can have only one active maptool
  • qgis.gui.QgsMapCanvas have mouseReleaseEvent event but I cannot figure out how to use it as its not a signal

I cannot find a signal that triggers when the actual geometry editing starts and in the signal I want to receive the coordinates of the mouse release point.

I am planing to implement the mouse release event using PyQt5.QtWidgets.QGraphicsView its the base class of qgis.gui.QgsMapCanvas

1 Answer 1


There is quite a bit of logic involved in your question.

Here is some example code which you can run in the Python console to get an idea of how this might work for you.

Basically, you should create a custom edit tool which sub-classes QgsMapToolEdit. Then you can re-implement the methods canvasReleaseEvent() and canvasMoveEvent(), and use the snapPoint() and isSnapped() methods to a) snap the click points to a point layer if it within the snapping tolerance and b) check if the start and end points of the new line feature are snapped to a point. Then, after right click to finish digitizing, if correct conditions are met, create a feature object, set it's geometry from the temporary rubber band geometry and add the feature to the line layer data provider. The code below also shows how to create the rubber band to assist with visualizing your digitized features and how to push an error message to the message bar.

I hope this helps you achieve your goal.

A couple of notes:

  1. Set the desired snapping options on your point layer first.

  2. Make sure your line layer and project CRS are the same

Now just select your line layer, set it to edit mode and run the code. Use left click to start digitizing, right click to finish.

class TestEditTool(QgsMapToolEdit):

    def __init__(self, canvas):
        self.canvas = canvas
        QgsMapToolEdit.__init__(self, self.canvas)
        self.rb = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry)
        self.fixed_points = []

    def canvasReleaseEvent(self, event):
        click_point = event.snapPoint()
        if event.button() == Qt.LeftButton:
            if len(self.fixed_points) < 1:
                if not event.isSnapped():
                    iface.messageBar().pushMessage('Edit Error', 'Start point of line feature \
                    must be snapped to point layer', level=Qgis.Warning, duration=3)
        elif event.button() == Qt.RightButton:
            if self.fixed_points:
                if not event.isSnapped():
                    iface.messageBar().pushMessage('Edit Error', 'End point of line feature \
                    must be snapped to point layer', level=Qgis.Warning, duration=3)
                    new_line = QgsLineString(self.fixed_points)
                    geom = QgsGeometry().fromPolyline(new_line)
                    ### Add Features here
                    layer = iface.activeLayer()
                    if layer.isEditable():
                        feat = QgsFeature(layer.fields(), layer.featureCount()+1)
                        atts = {layer.dataProvider().fieldNameIndex('id'): layer.featureCount()}
                        layer.dataProvider().changeAttributeValues({feat.id(): atts})

    def canvasMoveEvent(self, event):
        if self.fixed_points:
            if len(self.fixed_points) == 1:
                pt1 = self.fixed_points[0]
                pt2 = QgsPoint(event.snapPoint())
                rb_line = QgsLineString(pt1, pt2)
                self.rb.setToGeometry(QgsGeometry().fromPolyline(rb_line), QgsProject().instance().crs())
                rb_line = QgsLineString(self.fixed_points)
                self.rb.setToGeometry(QgsGeometry().fromPolyline(rb_line), QgsProject().instance().crs())

    def clean_up(self):

canvas = iface.mapCanvas()
t = TestEditTool(canvas)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.