pyqgis – Adding Fields to Layer in QGIS Using Python Plugin

fields-attributespyqgisqgis-plugins

I'm trying to write a plugin for QGIS that adds a field to a layer selected from a combobox and calculates area.

For that I import these modules:

from PyQt4.QtCore import QVariant, QSettings, QTranslator, qVersion, QCoreApplication
from PyQt4.QtGui import QAction, QIcon, QFileDialog

and in the run function:

def run(self):
    # here I populate a combo box in a form
    layers = self.iface.legendInterface().layers()
    layer_list = []
    for layer in layers:
        layer_list.append(layer.name())
    self.dlg.Cmb_Capa.addItems(layer_list)

    self.dlg.show()
    result = self.dlg.exec_()
    if result:

Now here comes my problem… I find this in the documentation page of QGIS to add a field to a layer

if caps & QgsVectorDataProvider.AddAttributes:
    res = layer.dataProvider().addAttributes([QgsField("mytext", QVariant.String), QgsField("myint", QVariant.Int)])

but I don't know how to take the layer selected in the combo and apply this.

Can anyone give me a hand or a tip about how to do it?

Best Answer

One method (which you are currently using) is the standard QComboBox. First we need to import some more classes so add this before importing PyQt4:

from qgis.core import QgsMapLayerRegistry, QgsField, QgsExpression
from PyQt4.QtCore import QVariant, QSettings, QTranslator, qVersion, QCoreApplication

Then in your run(self) function, we can use the following (I've added some comments which hopefully will help you understand what's going on):

def run(self):
    # here I populate a combo box in a form
    layers = self.iface.legendInterface().layers()
    layer_list = []
    for layer in layers:
        layer_list.append(layer.name())
    self.dlg.Cmb_Capa.addItems(layer_list)

    self.dlg.show()
    result = self.dlg.exec_()
    if result:
        # Read current text from combo box
        combo_layer = self.dlg.Cmb_Capa.currentText()
        # Identify layer from table of contents by matching name with the combo box text
        selected_layer = QgsMapLayerRegistry.instance().mapLayersByName(combo_layer)[0]
        # Add a "real" field type with the name "Area"
        selected_layer.dataProvider().addAttributes( [ QgsField("Area", QVariant.Double) ] )
        # Update the attribute table
        selected_layer.updateFields()
        # Allow editing of the selected layer
        selected_layer.startEditing()
        # Find index of the newly-created "Area" field
        idx = selected_layer.fieldNameIndex( "Area" )
        # Use standard QgsExpression to calculate area for each feature
        e = QgsExpression( """ $area """ )
        e.prepare( selected_layer.pendingFields() )
        for f in selected_layer.getFeatures():
            f[idx] = e.evaluate(f)
            selected_layer.updateFeature(f)
        # Save the selected layer
        selected_layer.commitChanges()

Alternatively, you could use QGIS custom widget: QgsMapLayerComboBox. This is automatically updated when any changes are made to the layer list (i.e. layers added/removed, names changed etc) without you having to refresh the plugin. To use this, you will need to import an additional class:

from qgis.gui import QgsMapLayerProxyModel

Now your run(self) function can look like this:

 def run(self):
    # Here, QgsMapLayerComboBox automatically reads the relevant layers
    self.dlg.Cmb_Capa.setFilters( QgsMapLayerProxyModel.PolygonLayer )

    self.dlg.show()
    result = self.dlg.exec_()
    if result:
        combo_layer = self.dlg.Cmb_Capa.currentText()
        selected_layer = QgsMapLayerRegistry.instance().mapLayersByName(combo_layer)[0]
        selected_layer.dataProvider().addAttributes( [ QgsField("Area", QVariant.Double) ] )
        selected_layer.updateFields()
        selected_layer.startEditing()
        idx = selected_layer.fieldNameIndex( "Area" )
        e = QgsExpression( """ $area """ )
        e.prepare( selected_layer.pendingFields() )
        for f in selected_layer.getFeatures():
            f[idx] = e.evaluate(f)
            selected_layer.updateFeature(f)
        selected_layer.commitChanges()

Note: When using QGIS custom widgets, you may need to edit your "plugin_name_dialog_base.ui" file, find the QgsMapLayerComboBox class and replace the <header> to qgis.gui.h.

For example, it might look like this:

<customwidget>
 <class>QgsMapLayerComboBox</class>
 <extends>QComboBox</extends>
 <header>qgsmaplayercombobox.h</header>
</customwidget>

And it needs to look like this:

<customwidget>
 <class>QgsMapLayerComboBox</class>
 <extends>QComboBox</extends>
 <header>qgis.gui.h</header>
</customwidget>
Related Question