3

Problem: I need a form widget that displays and allows the multi-selection of features from a layer ("POIs") within a defined radius (e.g., 3000m). Ideally, the features should be sorted by distance. The selected features should be stored in a list.

Background: I believe QGIS can handle this, but I haven't found a ready-made solution. I'm also willing to integrate a QML widget / Python into my forms. However, I don't want to use QT Designer for custom UIs, as I foresee other problems with that approach. My current forms are extremely complex but work smoothly, and I want to stick with drag-and-drop functionality.

Question: Is it possible to solve this problem using QGIS's built-in tools?

Additional Information: I'm using QGIS version 3.24.3. I'm familiar with Python and QML programming. I'm willing to learn new things if necessary.

0

1 Answer 1

7
+100

You can make use of an Action on the attribute form.

enter image description here

Once the Action is configured, you can drag it into your form as usual.

enter image description here

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

enter image description here

1
  • Great, that is the core of the function I need, but I also need a Form Widget, where the user can choose the points in the selection area he wants to (multi selection).
    – MartinMap
    Commented Mar 17 at 6:26

Your Answer

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

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