You would probably need to create a custom map tool. For starters read about using the map canvas and creating map tools at the PyQGIS developer cookbook at https://docs.qgis.org/3.28/en/docs/pyqgis_developer_cookbook/canvas.html# .
The actual line drawing is done using rubber bands ( https://qgis.org/pyqgis/master/gui/QgsRubberBand.html ).
I have implemented a static version of your requirements. Is has hardcoded points for the points the user clicked and the mouse position. It assumes the current map layer is a projected map layer.
The points are in central Germany and the coordinates are in EPSG:3857.
I am not sure of the distance calculation, so please take it only as a demonstration and a hint on how it can be done, not as a final correct calculation, but it corresponds quite well with the QGIS interface length measurement.
My QGIS version is 3.28.3
def createDistanceAnnotation(p1, p2):
"""create an annotation of the distance between two points.
Parameters p1 and p2 are of type QgsPoint
Adapted from https://gis.stackexchange.com/a/421040/15183 by CodeBard"""
line = QgsLineString(p1, p2)
line_geom = QgsGeometry(line)
# calculate length of line
d = QgsDistanceArea()
d.setEllipsoid('WGS84')
d.setSourceCrs(qgis.utils.iface.activeLayer().crs(), QgsProject.instance().transformContext())
length = d.measureLength(line_geom)
# calculate gradient of line to place label at an angle
gradient = QgsGeometryUtils.gradient(p1, p2)
# some math to convert gradient to an angle in degrees
angle = math.degrees(math.atan(gradient))
(x, y) = QgsGeometryUtils.perpendicularOffsetPointAlongSegment( p1.x(), p1.y(), p2.x(), p2.y(), 0.5, 400 )
# distance Unit enum has changed in 3.30. See https://qgis.org/pyqgis/master/core/Qgis.html#qgis.core.Qgis.DistanceUnit .
label = QgsAnnotationPointTextItem(d.formatDistance(length, 2,QgsUnitTypes.DistanceUnit.DistanceMeters), QgsPointXY(x, y))
label.setAngle(angle)
return label
def resetRB():
"""resets all created rubber bands and the annotation layer"""
for rb in createdRubberBands:
rb.reset()
QgsProject.instance().mainAnnotationLayer().reset()
# this stores the points the user has already clicked on
clickedPoints = [
QgsPoint(1069980,6958263), # walsrode
QgsPoint(1053886,6890568), # neustadt
QgsPoint(922838,6936805) # vechta
]
# this stores the current mouse position
mousePosition = QgsPoint(978272,6929652) # middle
# this stores the rubber bands that are created between the clicked points and the mouse position
createdRubberBands = []
# the layer where the lengths will be shown on
annotation_layer = QgsProject.instance().mainAnnotationLayer()
# draw line between each clicked point and the mouse position and draw annotation
for p in clickedPoints:
rb = QgsRubberBand(iface.mapCanvas() , QgsWkbTypes.LineGeometry)
rb.setToGeometry(QgsGeometry.fromPolyline([p, mousePosition]), QgsCoordinateReferenceSystem("EPSG:3857"))
rb.setColor(QColor(255, 0, 0))
rb.setWidth(2)
createdRubberBands.append(rb)
annotation_layer.addItem(createDistanceAnnotation(p, mousePosition))