QGIS 3.22.3自定义插件中QgsMapToolEmitPoint/QgsMapToolIdentifyFeature初始化后无响应问题求助
Hey, let's work through your QGIS plugin issue step by step. First, let's recap what you're trying to do: you're building a plugin with a dock widget button that triggers a map tool to select/identify features from the spots layer, then pass their attributes to a database function. You ran into problems where subclasses of QgsMapToolEmitPoint and QgsMapToolIdentifyFeature didn't respond to canvas clicks, but a QgsMapTool subclass worked—though you're unsure how to get features with it.
Let's start by looking at your code and the root causes of the unresponsive tools, then walk through solutions for both QgsMapToolIdentifyFeature (the easier path for your use case) and QgsMapTool if you need it.
Your Current Code Snippets
Button Connection Logic
def connect_the_buttons(self): # get spots b = self.dock.select_spots_tool b.setEnabled(True) b.setCheckable(True) b.clicked.connect(self.getSpots)
Button Click Handler
def getSpots(self, checked): if checked: spot_id_List.clear() t = self.selectSpots(self.iface) #t = gs.selectSpots(self.iface) self.iface.mapCanvas().setMapTool(t) QApplication.setOverrideCursor(Qt.CrossCursor) spots = QgsProject.instance().mapLayersByName('spots')[0] self.iface.setActiveLayer(spots) tType = type(t) log(f'what is t? >> {type(t)}') log(f'{tType}') log(f'type({t})') log(f'{self.selectSpots}') else: log('not checked') QApplication.restoreOverrideCursor() spot_id_List.clear()
Problematic MapTool Subclass
class selectSpots(QgsMapToolEmitPoint): #canvasClicked = pyqtSignal('QgsPointXY') # def __new__(cls, *args, **kwargs): # return super(selectSpots, cls).__new__(cls, *args, **kwargs) def __init__(self, canvas): log('selectSpots __init__ function is running...') self.canvas = self.iface.mapCanvas() #QgsMapTool.__init__(self, canvas) #self.layer = self.iface.activeLayer() #self.iface.canvasClicked.connect(self.id_spots) #c = iface.mapCanvas() #activeSpots = iface.activeLayer() QgsMapToolEmitPoint.__init__(self, self.canvas) #self.iface.layer().connect(self.getLayerId) #self.iface.currentLayerChanged.connect(self.active_changed) #QApplication.setOverrideCursor(Qt.CrossCursor) # def getLayerId(self): # log('connected_to_click layer event') # def id_spots(self): # log('this happend when i clicked') def activate(self): log('The activate method was called.') def canvasDoubleClickEvent(self, e): log('Double Clicked') log(f'double click event: {e}') # def active_changed(self, layer): # activeSpots.removeSelection() # spot_id_List.clear() def canvasReleaseEvent(self, event): log('canvas release event recorded') def canvasPressEvent(self, event): log('canvas press event recorded') point = event.mapPoint() self.canvasClicked.emit(point) log(f'point: {point}') # log(f'event: {event}') #self.handle_spot_selection() # log(f'on press: spot_id_List: {spot_id_List}') # selectedLayer = self.iface.activeLayer() # selection = selectedLayer.selectedFeatures() #self.iface.actionSelect().trigger() # for f in selection: # spot_id_List.append(f.attribute('id')) # log(f'after press: spot_id_List: {spot_id_List}')
Why Your QgsMapToolEmitPoint Subclass Isn't Working
The main issue here is incorrect initialization:
- Your
selectSpotsclass expects acanvasparameter, but you're passingself.ifacewhen creating the tool ingetSpots()—that's a mismatch. - Inside
__init__, you're trying to accessself.ifacebut never initialize it, which would throw an error (though your log shows__init__runs, so maybe you have it defined elsewhere, but it's still messy). - You're redefining
self.canvasinstead of using the passed parameter, which breaks the parent class setup.
Solution 1: Use QgsMapToolIdentifyFeature (Best for Your Use Case)
QgsMapToolIdentifyFeature is built exactly for what you need: clicking to identify features on a specific layer. Here's a cleaned-up implementation:
from qgis.gui import QgsMapToolIdentifyFeature class selectSpots(QgsMapToolIdentifyFeature): def __init__(self, iface): self.iface = iface canvas = iface.mapCanvas() # Initialize parent class with the canvas super().__init__(canvas) # Set the target layer (you can update this dynamically if needed) self.spots_layer = QgsProject.instance().mapLayersByName('spots')[0] self.setLayer(self.spots_layer) # Connect the featureIdentified signal to your handler self.featureIdentified.connect(self.on_feature_identified) def on_feature_identified(self, feature): # This runs every time a feature is clicked spot_id = feature['id'] log(f'Identified feature with ID: {spot_id}') # Add the ID to your list or pass it to your database function spot_id_List.append(spot_id) def activate(self): # Optional: Add logic when the tool is activated log('Spot selection tool activated') super().activate() # Don't forget to call the parent activate method!
Then update your getSpots() method to initialize the tool correctly:
def getSpots(self, checked): if checked: spot_id_List.clear() # Pass iface to the tool, not canvas t = self.selectSpots(self.iface) self.iface.mapCanvas().setMapTool(t) QApplication.setOverrideCursor(Qt.CrossCursor) spots = QgsProject.instance().mapLayersByName('spots')[0] self.iface.setActiveLayer(spots) log(f'Activated tool type: {type(t)}') else: log('Spot selection tool deactivated') QApplication.restoreOverrideCursor() spot_id_List.clear()
Solution 2: Use QgsMapTool to Get Features
If you want to stick with QgsMapTool, you can manually query features at the click location using a search radius (to account for mouse precision). Here's how:
from qgis.gui import QgsMapTool from qgis.core import QgsRectangle, QgsFeatureRequest class selectSpots(QgsMapTool): def __init__(self, iface): self.iface = iface canvas = iface.mapCanvas() super().__init__(canvas) self.setCursor(Qt.CrossCursor) def canvasPressEvent(self, event): # Get the clicked point in map coordinates point = event.mapPoint() layer = self.iface.activeLayer() # Validate the active layer if not layer or layer.type() != QgsMapLayer.VectorLayer: log('No valid vector layer selected') return # Create a small search rectangle around the click (adjust radius as needed) pixel_radius = 5 # 5 pixels around the click map_radius = self.canvas().mapUnitsPerPixel() * pixel_radius search_rect = QgsRectangle( point.x() - map_radius, point.y() - map_radius, point.x() + map_radius, point.y() + map_radius ) # Query features in the rectangle request = QgsFeatureRequest().setFilterRect(search_rect) for feature in layer.getFeatures(request): # Check if the point is inside the feature geometry if feature.geometry().contains(point): spot_id = feature['id'] log(f'Selected feature ID: {spot_id}') spot_id_List.append(spot_id) break # Stop after finding the first matching feature
Key Tips to Avoid Future Issues
- Always call the parent class's
__init__andactivatemethods: Skipping these breaks the core map tool functionality. - Match parameters when initializing classes: If your tool needs access to
iface, pass it explicitly instead of relying on global variables. - Use built-in signals and tools: QGIS provides specialized map tools for most common tasks (like
QgsMapToolIdentifyFeaturefor feature selection) so you don't have to reinvent the wheel.
内容的提问来源于stack exchange,提问作者Johnny D

