You can make use of an Action on the attribute form.
Once the Action is configured, you can drag it into your form as usual.
The code
Here I subclass QgsMapToolEmitPoint
and give it some custom functionality. Namely, leveraging QgsSpatialIndex
to find the nearest features to the point clicked on the map canvas within a given distance. Conveniently, the list of feature IDs returned by the QgsSpatialIndex.nearestNeighbor()
method are already sorted by distance.
I perform a selection of the found features to provide some visual feedback.
The tool can be unset by pressing the X key.
I am sure there is a more elegant way of displaying the returned feature IDs, this was a quick hack. Suggestions welcome.
from qgis.PyQt.QtCore import Qt
from qgis.PyQt.QtWidgets import QLabel
from qgis.gui import QgsMapToolEmitPoint
from qgis.utils import iface
class GetFeaturesInRadius(QgsMapToolEmitPoint):
def __init__(self, canvas):
super().__init__(canvas)
self.canvas = iface.mapCanvas()
self.lyr = iface.activeLayer()
self.num_feats = self.lyr.featureCount()
self.features = self.lyr.getFeatures()
self.index = QgsSpatialIndex(
self.features,
# full feature geometry is stored,
# otherwise only the feature bounding box is used
flags=QgsSpatialIndex.FlagStoreFeatureGeometries
)
# get the layour of the attribute form
self.layout = form.children()[1]
item_count = self.layout.count()
# remove any QLabels already in the layout
# (otherwise they stack up each time the action is triggered)
for i in range(item_count):
item = self.layout.itemAt(i)
if isinstance(item, QLabel):
self.layout.removeWidget(item)
# add a label to the attribute form to display the found feature IDs
self.label = QLabel()
self.layout.addWidget(self.label)
# fire the handle_click method when the canvas is clicked
self.canvasClicked.connect(self.handle_click)
def handle_click(self, evt):
self.lyr.removeSelection()
# evt is a QgsPointXY of the click location
# num_feats is the maximum number of features for nearestNeighbor to find
# 200 is the distance which you will need to adjust (perhaps dynamically somehow)
fids = self.index.nearestNeighbor(evt, self.num_feats, 200)
# select the features for visual feedback and write the IDs to the label
self.lyr.selectByIds(fids)
self.label.setText(', '.join([str(id) for id in fids]))
# override the keyPressEvent of QgsMapToolEmitPoint
# to deactivate the tool when X is pressed
def keyPressEvent(self, event):
if event.key() == Qt.Key_X:
self.lyr.removeSelection()
self.canvas.unsetMapTool(self)
self.layout.removeWidget(self.label)
# instantiate the tool and set it on the map canvas
canvas = iface.mapCanvas()
tool = GetFeaturesInRadius(canvas)
canvas.setMapTool(tool)
Result