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?

PolyGeo
65.5k29 gold badges115 silver badges349 bronze badges
asked Aug 1, 2020 at 20:10
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. Commented 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. Commented Aug 2, 2020 at 23:23
  • I guess as a supplement, would this be easier to implement by making a plugin instead? Commented 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()
answered Aug 4, 2020 at 11:56
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! Commented Aug 5, 2020 at 6:15

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.