1

I have tried this code and several other similar versions and I always get this error See Writing Custom Map Tools using RectangleMapTool

QgsMapToolEmitPoint.__init__(self, self.canvas)
 TypeError: QgsMapToolEmitPoint(): argument 1 has unexpected type 'sip.wrappertype'

I just can't seem to find a solution..

class MyMapTool(QgsMapToolEmitPoint):
    def __init__(self, canvas):
        QgsMapToolEmitPoint.__init__(self, canvas)
        self.rubberBand = QgsRubberBand(canvas, True)
        self.rubberBand.setColor(QColor(255, 0, 0))
        self.rubberBand.setWidth(2)

    def canvasPressEvent(self, e):
        point = self.toMapCoordinates(e.pos())
        self.rubberBand.addPoint(point)
        self.rubberBand.show()

    def canvasMoveEvent(self, e):
        point = self.toMapCoordinates(e.pos())
        self.rubberBand.movePoint(point)

    def canvasReleaseEvent(self, e):
        point = self.toMapCoordinates(e.pos())
        self.rubberBand.addPoint(point)

1 Answer 1

3

You have not shown any code where you create an instance of your custom map tool, but I suspect that the cause of the error in your question lies there. I am guessing here but maybe you tried to do something like this:

map_tool = MyMapTool(QgsMapCanvas)

Passing QgsMapCanvas with no params, would give you that error (because if you run: print(type(QgsMapCanvas)) in the Python console you will see that it returns <class 'sip.wrappertype'>).

The QgsMapToolEmitPoint class constructor expects a QgsMapCanvas() object, i.e. an instance of the class. If you are running this in the Python console and working with the main QGIS map canvas, you should retrieve it like iface.mapCanvas() (or probably self.iface.mapCanvas() in a plugin).

A couple of other issues with your code:

  1. It is a good idea to store the QgsMapCanvas object passed to the map tool as an instance attribute (e.g. self.canvas).

  2. The second argument of the QgsRubberBand constructor should be the geometry type you are going to use for the rubber band.

E.g.

QgsRubberBand(canvas, QgsWkbTypes.PolygonGeometry)

Note that from 3.30 onwards, you should use:

QgsRubberBand(canvas, Qgis.GeometryType.Polygon)

Below are a couple of working examples of map tools using different rubber bands. The first is click and drag rectangle rubber band, the second allows for digitising a polygon using multiple consecutive mouse clicks (with right-click to finish). Both of these example work when pasted into a blank editor in the Python console and run from there. See the end of this answer for an example of using these in a plugin.

Drag rectangle tool:

class RectangleMapTool(QgsMapToolEmitPoint):
    def __init__(self, canvas):
        self.canvas = canvas
        QgsMapToolEmitPoint.__init__(self, self.canvas)
        self.start_point = None
        self.end_point = None
        self.rubber_band = None
        
    def canvasPressEvent(self, e):
        if self.rubber_band:
            self.rubber_band.reset()
        self.rubber_band = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry)
        self.rubber_band.setStrokeColor(Qt.red)
        self.rubber_band.setWidth(1)
        self.start_point = self.toMapCoordinates(e.pos())
        self.end_point = self.start_point
    
    def canvasMoveEvent(self, e):
        if self.start_point:
            self.end_point = self.toMapCoordinates(e.pos())
            self.rubber_band.reset()
            r = self.get_rectangle()
            self.rubber_band.setToGeometry(QgsGeometry().fromRect(r))
                
    def canvasReleaseEvent(self, e):
        self.end_point = self.toMapCoordinates(e.pos())
        r = self.get_rectangle()
        self.rubber_band.setToGeometry(QgsGeometry().fromRect(r))
        self.start_point = None
        self.end_point = None
        
    def get_rectangle(self): 
        rect = QgsRectangle(self.start_point, self.end_point)
        return rect
        
    def deactivate(self):
        if self.rubber_band:
            self.rubber_band.reset()
            
map_canvas = iface.mapCanvas()
map_tool = RectangleMapTool(map_canvas)
map_canvas.setMapTool(map_tool)

Draw polygon tool:

class PolygonMapTool(QgsMapToolEmitPoint):
    def __init__(self, canvas):
        self.canvas = canvas
        QgsMapToolEmitPoint.__init__(self, self.canvas)
        self.rubber_band_line = None
        self.rubber_band_fixed = None
        self.points = []
        self.drawing = False
        
    def canvasPressEvent(self, e):
        set_point = self.toMapCoordinates(e.pos())
        if self.rubber_band_fixed:
            self.rubber_band_fixed.reset()
        if e.button() == Qt.LeftButton:
            self.drawing = True
            self.points.append(set_point)
            if len(self.points) == 2:
                self.rubber_band_fixed = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry)
                self.rubber_band_fixed.setStrokeColor(QColor(255, 0, 0, 150))
                self.rubber_band_fixed.setWidth(2)
                geom = QgsGeometry().fromPolylineXY(self.points)
                self.rubber_band_fixed.setToGeometry(geom)
                self.rubber_band_fixed.show()
            elif len(self.points) > 2:
                self.rubber_band_fixed = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry)
                self.rubber_band_fixed.setStrokeColor(QColor(255, 0, 0, 150))
                self.rubber_band_fixed.setWidth(2)
                pnt_ring = [p for p in self.points]
                pnt_ring.append(pnt_ring[0])
                geom = QgsGeometry().fromPolygonXY([pnt_ring])
                self.rubber_band_fixed.setToGeometry(geom)
                self.rubber_band_fixed.show()
        elif e.button() == Qt.RightButton:
            self.drawing = False
            if len(self.points) <= 2:
                self.clean_up()
            elif len(self.points) > 2:
                if self.rubber_band_line is not None:
                    self.rubber_band_line.reset()
                # Here you could create a QgsFeature instead of a rubber band
                pnt_ring = [p for p in self.points]
                pnt_ring.append(pnt_ring[0])
                geom = QgsGeometry().fromPolygonXY([pnt_ring])
                # and set the feature geometry to geom
                self.rubber_band_fixed.setToGeometry(geom)
                self.rubber_band_fixed.show()
                self.points.clear()
                
    def canvasMoveEvent(self, e):
        if self.drawing:
            point = self.toMapCoordinates(e.pos())
            if self.rubber_band_line is not None:
                self.rubber_band_line.reset()
            if self.points:
                self.rubber_band_line = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry)
                self.rubber_band_line.setLineStyle(Qt.DashLine)
                self.rubber_band_line.setColor(QColor('Grey'))
                if len(self.points) == 1:
                    geom = QgsGeometry().fromPolylineXY([self.points[0], point])
                    self.rubber_band_line.setToGeometry(geom)
                    self.rubber_band_line.show()
                elif len(self.points) > 1:
                    geom = QgsGeometry().fromPolylineXY([self.points[0], point, self.points[-1]])
                    self.rubber_band_line.setToGeometry(geom)
                    self.rubber_band_line.show()
    
    def clean_up(self):
        if self.rubber_band_line is not None:
            self.rubber_band_line.reset()
            self.rubber_band_line = None
        if self.rubber_band_fixed is not None:
            self.rubber_band_fixed.reset()
            self.rubber_band_fixed = None
        self.points.clear()
        
    def deactivate(self):
        self.clean_up()
        
map_canvas = iface.mapCanvas()
map_tool = PolygonMapTool(map_canvas)
map_canvas.setMapTool(map_tool)

I have also packaged these into a minimal example plugin and uploaded to GitHub repo. You can download a zip file using the green Code button and install it in QGIS to test it out.

Here is a short screencast showing the behaviour of the two map tools activated via the plugin:

enter image description here

4
  • Thanks, that appears to have solved the error but now the events ae not firing :( That is not a problem for this thread.
    – Bandit253
    Sep 16, 2023 at 22:41
  • Thanks again Ben, I was initially trying the linked example from the above, "PyQGIS Developer Cookbook 9. Using the Map Canvas". In desperation, I tried the other copied I copied in the above code. I am calling just from a button on a plugin panel.
    – Bandit253
    Sep 17, 2023 at 2:30
  • initially I was thinking to start with a rectangle but would have ultimately liked an irregular polygon.
    – Bandit253
    Sep 17, 2023 at 4:19
  • You're an absolute champion - thanks
    – Bandit253
    Sep 18, 2023 at 4:34

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.