0

I am trying to build a script (python 3) where a user can select a number of numeric fields, define a set of numbers, and the script will create a new field after multiplying the field with the numbers. For instance, a user can select 4 different fields A, B, C, D, then define 4 corresponding float numbers 0.2, 0.3, 0.1, 0.4. The script will then use field calculator to create 4 new fields of A x 0.2, B x 0.3, C x 0.1, D x 0.4.

So far I'm able to make it work if I specify how many fields at start of the script, but is there any way to build this script that can allow the user to pick however many fields they want?

3
  • 1
    Just to clarify, do you want to populate the new fields with the numeric values in the original fields multiplied by the specified float numbers for each feature in the layer. It's not entirely clear to me how you want to name and populate the new fields.
    – Ben W
    Aug 2, 2020 at 10:44
  • Yes, basically I'd like to have the selected fields multiplied by each of the corresponding float numbers. It doesn't have to be a new field created for every multiplying action, however. So in the example I used in the question, the result of the script can be a single new field created that is the sum of Field A x 0.2, Field B x 0.3, ... at each location. So far as a field calculator procedure. Hope this helps to clarify the question.
    – WFL.GIS
    Aug 2, 2020 at 23:23
  • I guess as a supplement, would this be easier to implement by making a plugin instead?
    – WFL.GIS
    Aug 3, 2020 at 10:15

1 Answer 1

1

Here is an of an example of how you could achieve your goal of allowing the user to select any number of available fields. This is how I would approach it anyway! Since you want to get user input, I am assuming you want some kind of simple GUI. You could use a few simple widgets like a QgsMapLayerComboBox, a QListWidget, a QTableWidget and a couple of QPushButtons. Copy the code at the end of this answer into an editor in the Python console and run. You will see a dialog like below. The field list is populated with only the numeric fields belonging to the current layer in the combo box and updates (by the currentLayerChanged() signal) when the current layer in the combo box is changed.

enter image description here

Select multiple fields from the list widget and click the 'Insert selected fields' button. The field names will be inserted into the first column of the table widget (these items are not enabled to avoid inadvertently changing them). You can then double click in the cells on the right to manually type in any number by which you wish to multiply the field value. See below:

enter image description here

Finally click the 'Create new field' button. A field called 'Calculated' will be created in the selected layer and populated for each feature with the sum of all selected field values multiplied by their respective constants (I think that is what you wanted). Below is the result on the layer in my example (the Alaska airports data set).

enter image description here

Here is the complete code for the UI and the logic. As I said, this just works when run from the Python console in QGIS but you could certainly implement something like this in a plugin if you wanted.

"""
/***************************************************************************
Copyright:  (C) Ben Wirf
Date:       August 2020
Email:      [email protected]
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
****************************************************************************/
"""

class myDialog(QDialog):
    def __init__(self):
        QDialog.__init__(self)
        self.setWindowTitle('Test Dialog')
        self.setGeometry(150, 150, 500, 500)
        self.layout = QGridLayout(self)
        self.cb_layers = QgsMapLayerComboBox(self)
        self.cb_layers.setFilters(QgsMapLayerProxyModel.VectorLayer)
        self.cb_layers.layerChanged.connect(self.load_fields)
        self.lw_fields = QListWidget(self)
        self.lw_fields.addItems([str(f.name()) for f in
            self.cb_layers.currentLayer().fields() if f.isNumeric()])
        self.lw_fields.setSelectionMode(QAbstractItemView.MultiSelection)
        self.btn_insert_flds = QPushButton('Insert selected fields', self)
        self.btn_insert_flds.clicked.connect(self.insert_fields)
        self.tbl_new_fields = QTableWidget(self)
        self.tbl_new_fields.setRowCount(1)
        self.tbl_new_fields.setColumnCount(2)
        self.tbl_new_fields.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.tbl_new_fields.setHorizontalHeaderLabels(['Field', 'Constant'])
        self.btn_create_flds = QPushButton('Create new field', self)
        self.btn_create_flds.clicked.connect(self.create_field)
        self.layout.addWidget(self.cb_layers, 0, 0)
        self.layout.addWidget(self.lw_fields, 1, 0)
        self.layout.addWidget(self.btn_insert_flds, 2, 0)
        self.layout.addWidget(self.tbl_new_fields, 3, 0)
        self.layout.addWidget(self.btn_create_flds, 4, 0)
        
    def load_fields(self):
        self.lw_fields.clear()
        self.lw_fields.addItems([str(f.name()) for f in 
            self.cb_layers.currentLayer().fields() if f.isNumeric()])
            
    def insert_fields(self):
        selected_flds = self.lw_fields.selectedItems()
        flags = Qt.ItemIsEnabled
        if len(selected_flds) > 0:
            self.tbl_new_fields.setRowCount(len(selected_flds))
            for current, f in enumerate(selected_flds):
                new_item = QTableWidgetItem(str(f.text()))
                new_item.setFlags(flags)
                self.tbl_new_fields.setItem(current, 0, new_item)
        self.lw_fields.clearSelection()
        
    def create_field(self):
        layer = self.cb_layers.currentLayer()
        pr = layer.dataProvider()
        pr.addAttributes([QgsField('Calculated', QVariant.Double, len=12, prec=3)])
        layer.updateFields()
        fld_index = layer.fields().lookupField('Calculated')
        num_cols = self.tbl_new_fields.columnCount()
        num_rows = self.tbl_new_fields.rowCount()
        for f in layer.getFeatures():
            attribute_map = {}
            calcs = []
            for row in range(num_rows):
                atts = []
                for col in range(num_cols):
                    tbl_item = self.tbl_new_fields.item(row, col).text()
                    atts.append(tbl_item)
                result = f[atts[0]]*float(atts[1])
                calcs.append(result)
            summed_calcs = sum(calcs)
            atts = {}
            atts[fld_index] = summed_calcs
            attribute_map[f.id()] = atts
            with edit(layer):
                pr.changeAttributeValues(attribute_map)
    
    
w = myDialog()
w.show()
1
  • Amazing! This is basically what I was looking for - the exact calculations I have in mind are more complex (requiring ordering the selected variables at each location), but it should be the same idea. I will look to see if I can implement the same thing in a plugin, as I am trying to learn how to use the plugin builder. Thank you!
    – WFL.GIS
    Aug 5, 2020 at 6:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.