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:
Set the desired snapping options on your point layer first.
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.rb.setStrokeColor(QColor('Red'))
self.rb.setWidth(2.0)
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)
else:
self.fixed_points.append(QgsPoint(click_point))
else:
self.fixed_points.append(QgsPoint(click_point))
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)
else:
self.fixed_points.append(QgsPoint(click_point))
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)
feat.setGeometry(geom)
layer.dataProvider().addFeature(feat)
atts = {layer.dataProvider().fieldNameIndex('id'): layer.featureCount()}
layer.dataProvider().changeAttributeValues({feat.id(): atts})
layer.triggerRepaint()
###
self.clean_up()
def canvasMoveEvent(self, event):
if self.fixed_points:
self.rb.reset()
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())
else:
rb_line = QgsLineString(self.fixed_points)
rb_line.addVertex(QgsPoint(event.snapPoint()))
self.rb.setToGeometry(QgsGeometry().fromPolyline(rb_line), QgsProject().instance().crs())
self.rb.show()
def clean_up(self):
self.rb.reset()
self.fixed_points.clear()
canvas = iface.mapCanvas()
t = TestEditTool(canvas)
canvas.setMapTool(t)