You need to override the canvasReleaseEvent() method inside the QgsMapTool class which you use to draw the rectangle on the canvas so that, as soon as the left mouse button is released after dragging the rectangle, you can retrieve the coordinates of the upper left and lower right corners and pass them to some processing method. The code example below should give you an idea of how this might work. You can paste this into an editor in the Python console and click run.
map_canvas = iface.mapCanvas()
class dragRectangle(QgsMapToolEmitPoint):
def __init__(self, canvas):
self.canvas = canvas
QgsMapToolEmitPoint.__init__(self, self.canvas)
self.start_point = None
self.end_point = None
self.rubber_band = QgsRubberBand(self.canvas, True)
self.rubber_band.setStrokeColor(Qt.red)
self.rubber_band.setWidth(1)
self.msg = QMessageBox()
def canvasPressEvent(self, e):
self.start_point = self.toMapCoordinates(e.pos())
self.end_point = self.start_point
def canvasMoveEvent(self, e):
if self.start_point:
end_point = self.toMapCoordinates(e.pos())
self.rubber_band.reset()
p = self.get_rect_points(self.start_point, end_point)
self.rubber_band.setToGeometry(QgsGeometry.fromPolygonXY(p), None)
def canvasReleaseEvent(self, e):
self.end_point = self.toMapCoordinates(e.pos())
r = self.get_rectangle()
self.rubber_band.reset()
self.start_point = None
self.end_point = None
ulx = r.xMinimum()
uly = r.yMaximum()
lrx = r.xMaximum()
lry = r.yMinimum()
self.msg.setText("Upper left x: {} \nUpper left y: {}\nLower right x: {}\nLower right y: {}".format(ulx, uly, lrx, lry))
self.msg.show()
# instead of showing a mesage box you can call a method which does your
# processing and pass ulx, uly, lrx & lry parameters...
def get_rect_points(self, startPoint, endPoint):
points = [[QgsPointXY(startPoint.x(), startPoint.y()), QgsPointXY(endPoint.x(), startPoint.y()), QgsPointXY(endPoint.x(), endPoint.y()), QgsPointXY(startPoint.x(), endPoint.y())]]
return points
def get_rectangle(self):
rect = QgsRectangle(self.start_point, self.end_point)
return rect
T = dragRectangle(map_canvas)
map_canvas.setMapTool(T)
#T.deactivated.connect(lambda: print('Tool deactivated'))
Updated answer:
If you are creating a processing script which extends the QgsProcessingAlgorithm class, there is a much simpler solution.
You can use the QgsProcessingParameterExtent class inside the initAlgorithm() method which handles taking a user defined extent entered directly, calculated from a layer, from the current canvas extent or from a rectangle drawn on the canvas (you do not even need to create your own map tool class!). The extent can then be accessed inside the processAlgorithm() method by calling self.parameterAsExtent(). You should be able to get the idea from the example script below. You can save it as a .py file and add it to the processing toolbox to test how it works.
This is a modified version of a template script from underdark available here:
https://anitagraser.com/2018/03/25/processing-script-template-for-qgis3/
from qgis.PyQt.QtCore import QCoreApplication, QVariant
from qgis.core import (QgsField, QgsFeature, QgsFeatureSink, QgsFeatureRequest, QgsProcessing,
QgsProcessingAlgorithm, QgsProcessingParameterFeatureSource,
QgsProcessingParameterExtent, QgsProcessingParameterFeatureSink)
class ExAlgo(QgsProcessingAlgorithm):
INPUT = 'INPUT'
OUTPUT = 'OUTPUT'
EXTENT = 'EXTENT'
def __init__(self):
super().__init__()
def name(self):
return "exalgo"
def tr(self, text):
return QCoreApplication.translate("exalgo", text)
def displayName(self):
return self.tr("Example script")
def group(self):
return self.tr("Examples")
def groupId(self):
return "examples"
def shortHelpString(self):
return self.tr("Example script which demonstrates getting user defined\
extent with the QgsProcessingParameterExtent class")
def helpUrl(self):
return "https://qgis.org"
def createInstance(self):
return type(self)()
def initAlgorithm(self, config=None):
self.addParameter(QgsProcessingParameterFeatureSource(
self.INPUT,
self.tr("Input layer"),
[QgsProcessing.TypeVectorAnyGeometry]))
# The QgsProcessingParameterExtent class takes care of getting the
# user defined extent for the processing algorithm
self.addParameter(QgsProcessingParameterExtent(
self.EXTENT,
self.tr("Processing extent")))
self.addParameter(QgsProcessingParameterFeatureSink(
self.OUTPUT,
self.tr("Output layer"),
QgsProcessing.TypeVectorAnyGeometry))
def processAlgorithm(self, parameters, context, feedback):
source = self.parameterAsSource(parameters, self.INPUT, context)
# extent is a QgsRectangle object which you can use in your processing
extent = self.parameterAsExtent(parameters, self.EXTENT, context)
(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
source.fields(), source.wkbType(),
source.sourceCrs())
features = source.getFeatures(QgsFeatureRequest())
for feat in features:
if feat.geometry().intersects(extent):
out_feat = QgsFeature()
out_feat.setGeometry(feat.geometry())
out_feat.setAttributes(feat.attributes())
sink.addFeature(out_feat, QgsFeatureSink.FastInsert)
return {self.OUTPUT: dest_id}