3

I do have a script that collects coordinates from map canvas clicks and stores them in a list called coordinates.

Now I want to create a feature, set its geometry and attributes and add the feature to the data provider. However, I do get the error saying "QgsGeometry.fromPolygon(unknown-type): argument 1 has unexpected type 'list'"

Here's my code:

from PyQt4.QtCore import *
from PyQt4.QtGui import QColor, QInputDialog, QLineEdit
from qgis.core import QGis, QgsMapLayerRegistry
from qgis.gui import QgsMapToolEmitPoint, QgsRubberBand, QgsMapTool

# Create new virtual layer 
vlyr = QgsVectorLayer("Polygon", "temporary_polygons", "memory")
dprov = vlyr.dataProvider()

# Add field to virtual layer 
dprov.addAttributes([QgsField("name", QVariant.String),
                     QgsField("size", QVariant.Double)])

vlyr.updateFields()

# Access MapTool  
previousMapTool = iface.mapCanvas().mapTool()
myMapTool = QgsMapToolEmitPoint( iface.mapCanvas() )


# create empty list to store coordinates 
coordinates = []

# Access ID 
fields = dprov.fields() 

def drawBand( currentPos, clickedButton ):
    iface.mapCanvas().xyCoordinates.connect( drawBand )

    if myRubberBand and myRubberBand.numberOfVertices():
        myRubberBand.removeLastPoint()
        myRubberBand.addPoint( currentPos )


def mouseClick( currentPos, clickedButton ):
    if clickedButton == Qt.LeftButton and len(coordinates) == 0: 
        # create the polygon rubber band associated to the current canvas
        global myRubberBand 
        myRubberBand = QgsRubberBand( iface.mapCanvas(), QGis.Polygon )
        # set rubber band style
        color = QColor(78, 97, 114)
        color.setAlpha(190)
        myRubberBand.setColor(color)
        #Draw rubberband
        myRubberBand.addPoint( QgsPoint(currentPos) )
        coordinates.extend(currentPos)
        print coordinates
    if clickedButton == Qt.LeftButton and len(coordinates) > 0:
        myRubberBand.addPoint( QgsPoint(currentPos) )
        coordinates.extend(currentPos)
        print coordinates

    if clickedButton == Qt.RightButton:

         # open input dialog     
        (description, False) = QInputDialog.getText(iface. mainWindow(), "Description", "Description for Polygon at x and y", QLineEdit.Normal, 'My Polygon') 

        #create feature and set geometry             
        poly = QgsFeature() 
        geomP = QgsGeometry.fromPolygon([coordinates])
        poly.setGeometry(geomP) 

        #set attributes
        indexN = dprov.fieldNameIndex('name') 
        indexA = dprov.fieldNameIndex('size') 
        poly.setAttributes([QgsDistanceArea().measurePolygon(coordinates), indexA])
        poly.setAttributes([description, indexN])

        # add feature                 
        dprov.addFeatures([poly])
        vlyr.updateExtents()

        #add layer      
        iface.mapCanvas().refresh()
        QgsMapLayerRegistry.instance().addMapLayers([vlyr])

        #delete list
        del coordinates[:]


myMapTool.canvasClicked.connect( mouseClick )
iface.mapCanvas().setMapTool( myMapTool )

My understanding from reading similar question is that QgsGeometry.fromPolygon() requires a QgsPolygon object and that I probably have to use geometry.asPolygon() in order to get one. However, I fail to do so. Could s.o. enlighten me please?

1 Answer 1

2

As you already wrote:

QgsGeometry.fromPolygon() requires a QgsPolygon object and that I probably have to use geometry.asPolygon()

But you are delivering a list object (coordinates)... thus it can't work.

There are two possible solutions:

1. The easy way:

You already create the geometry by creating the rubberband. So if you don't need the list (coordinates) for any other reason then the geometry you can simply create the geometry from the rubberband. Simply change the line:

geomP = QgsGeometry.fromPolygon([coordinates])

to

geomP = myRubberBand.asGeometry()


2. The complicated way

If, for whatever reason, you have to create the polygon from the coordinates list, you have to create a polygon object out of the list by iterating over it and creating the points for the polygon. So a quick and dirty layout would be:

from PyQt4.QtCore import *
from PyQt4.QtGui import QColor, QInputDialog, QLineEdit, QPolygonF
from qgis.core import QGis, QgsMapLayerRegistry, QgsPoint
from qgis.gui import QgsMapToolEmitPoint, QgsRubberBand, QgsMapTool

# Create new virtual layer 
vlyr = QgsVectorLayer("Polygon", "temporary_polygons", "memory")
dprov = vlyr.dataProvider()

# Add field to virtual layer 
dprov.addAttributes([QgsField("name", QVariant.String),
                     QgsField("size", QVariant.Double)])

vlyr.updateFields()

# Access MapTool  
previousMapTool = iface.mapCanvas().mapTool()
myMapTool = QgsMapToolEmitPoint( iface.mapCanvas() )


# create empty list to store coordinates 
coordinates = []

# Access ID 
fields = dprov.fields() 

def drawBand( currentPos, clickedButton ):
    iface.mapCanvas().xyCoordinates.connect( drawBand )

    if myRubberBand and myRubberBand.numberOfVertices():
        myRubberBand.removeLastPoint()
        myRubberBand.addPoint( currentPos )


def mouseClick( currentPos, clickedButton ):
    if clickedButton == Qt.LeftButton and len(coordinates) == 0: 
        # create the polygon rubber band associated to the current canvas
        global myRubberBand 
        myRubberBand = QgsRubberBand( iface.mapCanvas(), QGis.Polygon )
        # set rubber band style
        color = QColor(78, 97, 114)
        color.setAlpha(190)
        myRubberBand.setColor(color)
        #Draw rubberband
        myRubberBand.addPoint( QgsPoint(currentPos) )


        coordinates.extend(QgsPoint(currentPos))
        print coordinates
    if clickedButton == Qt.LeftButton and len(coordinates) > 0:
        myRubberBand.addPoint( QgsPoint(currentPos) )
        coordinates.extend(QgsPoint(currentPos))
        print coordinates

    if clickedButton == Qt.RightButton:

         # open input dialog     
        (description, False) = QInputDialog.getText(iface. mainWindow(), "Description", "Description for Polygon at x and y", QLineEdit.Normal, 'My Polygon') 

        #create feature and set geometry             
        poly = QgsFeature() 

        # easier solution using simply the geometry of myRubberBand
        #geomP = myRubberBand.asGeometry() 

        # or create polygon from point list
        # creat float point = clickeck positions
        point = QPointF()
        # create  float polygon --> construcet out of 'point'
        list_polygon = QPolygonF()

        for x in xrange(0,len(coordinates)):
            # since there is no distinction between x and y values we only want every second value 
            if x%2 == 0:
                point.setX(coordinates[x])
                point.setY(coordinates[x+1])
                list_polygon.append(point)
        point.setX(coordinates[0])
        point.setY(coordinates[1])
        list_polygon.append(point)

        geomP = QgsGeometry.fromQPolygonF(list_polygon)

        poly.setGeometry(geomP) 
        #set attributes
        indexN = dprov.fieldNameIndex('name') 
        indexA = dprov.fieldNameIndex('size') 

        # measure Area by geometry geomP
        #poly.setAttributes([QgsDistanceArea().measure(geomP), description])

        # measure Area by list-of-QgsPoint 
        poly.setAttributes([QgsDistanceArea().measurePolygon(geomP.asPolygon()[0]), description])


        # add feature                 
        dprov.addFeatures([poly])
        vlyr.updateExtents()

        #add layer      
        iface.mapCanvas().refresh()
        QgsMapLayerRegistry.instance().addMapLayers([vlyr])

        #delete list
        del coordinates[:]
        iface.mapCanvas().scene().removeItem(myRubberBand)

myMapTool.canvasClicked.connect( mouseClick )
iface.mapCanvas().setMapTool( myMapTool )

However this is just a quick and dirty solution. A more sophisticated approach should make a distinction between the x and y values in the first place (in the list coordinates). Your current list (coordinates) does not. You can get these by simply refering to (in the def mouseClick):

currentPos.x() currentPos.y()

Then fill a list (2d) or dictionary with the x/y values and then iterate over it to create the polygon.


PS: Whether you choose option 1 or 2 your code will still produce an error in the line:

poly.setAttributes([QgsDistanceArea().measurePolygon(coordinates), indexA])

You need to deliver a list of QgisPoints to "QgsDistanceArea().measurePolygon". You are simply delivering a list of numbers (coordinates).

2
  • Wow, this helps a lot. Thanks for taking the time.
    – dreks
    Commented May 23, 2017 at 17:39
  • hey I added the complete code which is working for me (QGis 2.18) also the area calculation is working in this example, either by calculating from geometry or from list-of-qgis points (see comments in code)
    – swad
    Commented May 24, 2017 at 15:16

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.