1

I have created a QGIS Graphic Model using built-in Graphic Modeler. My model is supposed ask the user for an input layer, an overlay layer, and fields from the overlay layer.

The model then should calculate the area-weighted average of those fields for each feature in the input layer. The result will be an input layer with extra fields for each area-weighted average.

enter image description here

I have realized that the field input can't be used in graphic modeller and I have to turn it into a python script to perform this task. I am adding the script here. What I am having trouble is with how to use the input from the user in my field calculations and field names.

from qgis.core import QgsProcessing
from qgis.core import QgsProcessingAlgorithm
from qgis.core import QgsProcessingMultiStepFeedback
from qgis.core import QgsProcessingParameterVectorLayer
from qgis.core import QgsProcessingParameterField
from qgis.core import QgsProcessingParameterFeatureSink
import processing


class FutureLuHsg(QgsProcessingAlgorithm):

    def initAlgorithm(self, config=None):
        self.addParameter(QgsProcessingParameterVectorLayer('luhsgexisting', 'LU HSG', types=[QgsProcessing.TypeVectorPolygon], defaultValue=None))
        self.addParameter(QgsProcessingParameterVectorLayer('parcels', 'Parcels', types=[QgsProcessing.TypeVectorPolygon], defaultValue=None))
        self.addParameter(QgsProcessingParameterField('fieldtoaverage1', 'Field to Average', type=QgsProcessingParameterField.Numeric, parentLayerParameterName='luhsgexisting', allowMultiple=True, defaultValue=None))
        self.addParameter(QgsProcessingParameterFeatureSink('Joined', 'Joined', optional=True, type=QgsProcessing.TypeVectorAnyGeometry, createByDefault=True, defaultValue=None))
        self.addParameter(QgsProcessingParameterFeatureSink('Final', 'Final', type=QgsProcessing.TypeVectorAnyGeometry, createByDefault=True, defaultValue=None))
        self.addParameter(QgsProcessingParameterFeatureSink('Stats', 'Stats', type=QgsProcessing.TypeVectorAnyGeometry, createByDefault=True, defaultValue=None))

    def processAlgorithm(self, parameters, context, model_feedback):
        # Use a multi-step feedback, so that individual child algorithm progress reports are adjusted for the
        # overall progress through the model
        feedback = QgsProcessingMultiStepFeedback(6, model_feedback)
        results = {}
        outputs = {}

        # Field calculator
        alg_params = {
            'FIELD_LENGTH': 10,
            'FIELD_NAME': 'FID_Parcels',
            'FIELD_PRECISION': 3,
            'FIELD_TYPE': 1,
            'FORMULA': '$id',
            'INPUT': parameters['parcels'],
            'NEW_FIELD': True,
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['FieldCalculator'] = processing.run('qgis:fieldcalculator', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

        feedback.setCurrentStep(1)
        if feedback.isCanceled():
            return {}

        # Intersection
        alg_params = {
            'INPUT': outputs['FieldCalculator']['OUTPUT'],
            'INPUT_FIELDS': None,
            'OVERLAY': parameters['luhsgexisting'],
            'OVERLAY_FIELDS': parameters['fieldtoaverage1'],
            'OVERLAY_FIELDS_PREFIX': '',
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['Intersection'] = processing.run('native:intersection', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

        feedback.setCurrentStep(2)
        if feedback.isCanceled():
            return {}

        # Field calculator 2
        alg_params = {
            'FIELD_LENGTH': 10,
            'FIELD_NAME': 'Field1*Area',
            'FIELD_PRECISION': 2,
            'FIELD_TYPE': 0,
            'FORMULA': ' \"fieldtoaverage\"  *  $area',
            'INPUT': outputs['Intersection']['OUTPUT'],
            'NEW_FIELD': True,
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['FieldCalculator2'] = processing.run('qgis:fieldcalculator', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

        feedback.setCurrentStep(3)
        if feedback.isCanceled():
            return {}

        # Statistics by categories
        alg_params = {
            'CATEGORIES_FIELD_NAME': 'FID_Parcels',
            'INPUT': outputs['FieldCalculator2']['OUTPUT'],
            'VALUES_FIELD_NAME': 'Field1*Area',
            'OUTPUT': parameters['Stats']
        }
        outputs['StatisticsByCategories'] = processing.run('qgis:statisticsbycategories', alg_params, context=context, feedback=feedback, is_child_algorithm=True)
        results['Stats'] = outputs['StatisticsByCategories']['OUTPUT']

        feedback.setCurrentStep(4)
        if feedback.isCanceled():
            return {}

        # Join attributes by field value
        alg_params = {
            'DISCARD_NONMATCHING': False,
            'FIELD': 'FID_Parcels',
            'FIELDS_TO_COPY': 'SUM',
            'FIELD_2': 'FID_Parcels',
            'INPUT': outputs['FieldCalculator']['OUTPUT'],
            'INPUT_2': outputs['StatisticsByCategories']['OUTPUT'],
            'METHOD': 1,
            'PREFIX': 'Stats_',
            'OUTPUT': parameters['Joined']
        }
        outputs['JoinAttributesByFieldValue'] = processing.run('native:joinattributestable', alg_params, context=context, feedback=feedback, is_child_algorithm=True)
        results['Joined'] = outputs['JoinAttributesByFieldValue']['OUTPUT']

        feedback.setCurrentStep(5)
        if feedback.isCanceled():
            return {}

        # Field calculator 3
        alg_params = {
            'FIELD_LENGTH': 10,
            'FIELD_NAME': 'Field1_Weighted',
            'FIELD_PRECISION': 3,
            'FIELD_TYPE': 0,
            'FORMULA': ' \"Statssum\" / $area',
            'INPUT': outputs['JoinAttributesByFieldValue']['OUTPUT'],
            'NEW_FIELD': True,
            'OUTPUT': parameters['Final']
        }
        outputs['FieldCalculator3'] = processing.run('qgis:fieldcalculator', alg_params, context=context, feedback=feedback, is_child_algorithm=True)
        results['Final'] = outputs['FieldCalculator3']['OUTPUT']
        return results

    def name(self):
        return 'Future LU HSG'

    def displayName(self):
        return 'Future LU HSG'

    def group(self):
        return 'QGIS Tools'

    def groupId(self):
        return 'QGIS Tools'

    def createInstance(self):
        return FutureLuHsg()
2

You say: "What I am having trouble is with how to use the input from the user in my field calculations and field names".

Could you explain more about the trouble you are experiencing?

About processing parameter / user input

You access the user input using parameters[] and output[]. That is fine, but you can also access parameters as described in the QGIS user manual. Have a look at Writing new Processing algorithms as Python scripts.

Some examples from the documentation. To get a feature source:

    input_featuresource = self.parameterAsSource(parameters,
                                                 'INPUT',
                                                 context)

To get a double:

    doublepar = self.parameterAsDouble(parameters, 'BUFFERDIST',
                                        context)

I guess you can see the pattern :-). What you do here is to assign the values of your input parameters to variables for using them in your script. Place these early in your ProcessAlgorithm method (before you need them for your calculations).

Regarding your specific question

To access the input vector field you can use parameters['fieldtoaverage1']. Some examples from your code:

 'FIELD_NAME': parameters['fieldtoaverage1'] + '*Area',

 'FORMULA': parameters['fieldtoaverage1'] + ' * $area',
| improve this answer | |
  • Thanks, I have tried to read the documentation but I am unable to find the answer to this. How can I access the fieldtoaverage1 value in the Field Calculator 2 formula line? Any help specific to this will be appreciated – ar-siddiqui Feb 27 at 21:40
  • I realized that my question is not specific enough. I have posted a new question with fewer details and being more specific. gis.stackexchange.com/questions/352823/… I will delete this question once you reply there. Sorry for the inconvinience. – ar-siddiqui Mar 3 at 23:20
  • I have provided an answer to the new question. The answer includes code (modification of code in your question). – Håvard Tveite Mar 4 at 8:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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