0

I am creating a processing plugin in QGIS that has two steps.

  1. Write features to a FeatureSink.
  2. Convert the FeatureSink to a layer and perform native:joinattributesbylocation.

I am using the QGIS script template as a starting point.

I have two output parameters: one for each of the outputs of the second step but the script produces 3 temporary layers. It is writing the output of the FeatureSink to a scratch layer as well. How can I prevent this appearing? Ideally I would like to prevent the intermediate FeatureSink appearing at all, but I could also delete it at the end of the process.

Code snippet below:

    def initAlgorithm(self, config=None):
        mastermap_parameter = QgsProcessingParameterFeatureSource(
            self.INPUT,
            self.tr('\'Area\' data from OS Mastermap'),
            [QgsProcessing.TypeVectorAnyGeometry]
        )
        mastermap_parameter.setDefaultValue('OSMM_AREA')
        self.addParameter(mastermap_parameter)

        string1_parameter = QgsProcessingParameterString(
            self.STRING_1,
            self.tr('Select features with the following value...'),
            '',
            multiLine = False,
            optional = False
        )
        string1_parameter.setDefaultValue('Buildings')
        self.addParameter(string1_parameter)

        field1_parameter = QgsProcessingParameterField(
            self.FIELD_1,
            self.tr('...in the following attribute field.'),
            '',
            self.INPUT
        )
        field1_parameter.setDefaultValue('theme')
        self.addParameter(field1_parameter)
        
        string2_parameter = QgsProcessingParameterString(
            self.STRING_2,
            self.tr('Additionally, select features with the following value...'),
            '',
            multiLine = False,
            optional = True
        )
        string2_parameter.setDefaultValue('Building')
        self.addParameter(string2_parameter)
        
        field2_parameter = QgsProcessingParameterField(
            self.FIELD_2,
            self.tr('...in the following attribute field.'),
            '',
            self.INPUT,
            QgsProcessingParameterField.DataType.Any,
            allowMultiple = False,
            optional = True
        )
        field2_parameter.setDefaultValue('descriptivegroup')
        self.addParameter(field2_parameter)
        
        self.addParameter(
            QgsProcessingParameterFeatureSource(
                self.NRD_INPUT,
                self.tr('Data from National Receptor Dataset'),
                [QgsProcessing.TypeVectorAnyGeometry]
            )
        )

        self.addParameter(
            QgsProcessingParameterFeatureSink(
                self.OUTPUT,
                self.tr('Output data')
            )
        )
        
        self.addParameter(
            QgsProcessingParameterVectorDestination(
                self.OUTPUT_2,
                self.tr('OS Mastermap buildings without NRD')
            )
        )

    
    def processAlgorithm(self, parameters, context, feedback):
        source = self.parameterAsSource(
            parameters,
            self.INPUT,
            context)
        
        field_1 = self.parameterAsString(
            parameters,
            self.FIELD_1,
            context)
        
        string_1 = self.parameterAsString(
            parameters,
            self.STRING_1,
            context)
        
        field_2 = self.parameterAsString(
            parameters,
            self.FIELD_2,
            context)
        
        string_2 = self.parameterAsString(
            parameters,
            self.STRING_2,
            context)       
        
        (sink, dest_id) = self.parameterAsSink(
            parameters,
            self.OUTPUT,
            context,
            source.fields(),
            source.wkbType(),
            source.sourceCrs()
        )
        
        total = 100.0 / source.featureCount() if source.featureCount() else 0
        valid_features = []
        
        for current, feature in enumerate(source.getFeatures()):
            if feedback.isCanceled():
                break

            if (feature[field_1] == string_1 or feature[field_2] == string_2):
                sink.addFeature(feature, QgsFeatureSink.FastInsert)

            feedback.setProgress(int(current * total))
        
        nrd_input = self.parameterAsLayer(
            parameters,
            self.NRD_INPUT,
            context)
        
        mm_building_layer = QgsProcessingUtils.mapLayerFromString(
            dest_id,
            context)
        
        joined_layer = processing.run("native:joinattributesbylocation", {
                'DISCARD_NONMATCHING' : True,
                'INPUT' : mm_building_layer,
                'JOIN' : nrd_input,
                'JOIN_FIELDS' : [],
                'METHOD' : 0,
                'NON_MATCHING' : parameters['OUTPUT_2'],
                'OUTPUT' : parameters['OUTPUT'],
                'PREDICATE' : [0],
                'PREFIX' : ''
            },
            is_child_algorithm=True,
            context=context,
            feedback=feedback)
        
        if feedback.isCanceled():
            return {}
    
        return {} 
5
  • Please also show all the parameters you are adding in initAlgorithm() Commented Jan 19, 2024 at 16:05
  • I have updated the code snippet to include initAlgorithm() Commented Jan 20, 2024 at 5:05
  • You are writing features to your 'OUTPUT' parameter in the first step, and then you are using the same 'OUTPUT" parameter as the output for the join algorithm.. why? Do you need the features from the first part (where (feature[field_1] == string_1 or feature[field_2] == string_2)? Commented Jan 22, 2024 at 22:15
  • No, I only need the final output. My question is about how I write to a different variable in the first step. Commented Jan 24, 2024 at 12:03
  • What do you mean by "appearing"? What exactly is happening, what exactly do you want to happen instead? Commented Jan 29, 2024 at 10:12

1 Answer 1

1
+50

You can create an intermediate layer with the filtered features, using native:extractbyexpression (and without a feature sink at any stage):

class myTestAlg(QgsProcessingAlgorithm):
    INPUT = 'INPUT'
    NRD_INPUT = 'NRD_INPUT'
    STRING_1 = 'STRING_1'
    STRING_2 = 'STRING_2'
    FIELD_1 = 'FIELD_1'
    FIELD_2 = 'FIELD_2'
    OUTPUT = 'OUTPUT'
    OUTPUT_2 = 'OUTPUT_2'

    def createInstance(self):
        return myTestAlg()

    def name(self):
        return 'test'

    def displayName(self):
        return 'Test'

    def initAlgorithm(self, config=None):
        # mastermap_parameter
        self.addParameter(QgsProcessingParameterFeatureSource(
            self.INPUT,
            self.tr('\'Area\' data from OS Mastermap'),
            types=[QgsProcessing.TypeVectorAnyGeometry],
            defaultValue='OSMM_AREA')
        )
        # string1_parameter
        self.addParameter(QgsProcessingParameterString(
            self.STRING_1,
            self.tr('Select features with the following value...'),
            'Buildings',
            multiLine = False,
            optional = False)
        )
        # field1_parameter
        self.addParameter(QgsProcessingParameterField(
            self.FIELD_1,
            self.tr('...in the following attribute field.'),
            'theme',
            self.INPUT)
        )
        # string2_parameter
        self.addParameter(QgsProcessingParameterString(
            self.STRING_2,
            self.tr('Additionally, select features with the following value...'),
            'Building',
            multiLine = False,
            optional = True)
        )
        #field2_parameter
        self.addParameter(QgsProcessingParameterField(
            self.FIELD_2,
            self.tr('...in the following attribute field.'),
            'descriptivegroup',
            self.INPUT,
            QgsProcessingParameterField.DataType.Any,
            allowMultiple = False,
            optional = True)
        )
        self.addParameter(QgsProcessingParameterFeatureSource(
            self.NRD_INPUT,
            self.tr('Data from National Receptor Dataset'),
            [QgsProcessing.TypeVectorAnyGeometry])
        )
        self.addParameter(QgsProcessingParameterFeatureSink(
            self.OUTPUT,
            self.tr('Output data'))
        )
        self.addParameter(QgsProcessingParameterFeatureSink(
            self.OUTPUT_2,
            self.tr('OS Mastermap buildings without NRD'))
        )
    
    def processAlgorithm(self, parameters, context, feedback):
        results = {}
        outputs = {}

        # Extract by expression
        field_1 = self.parameterAsString(parameters, 'FIELD_1', context)
        field_2 = self.parameterAsString(parameters, 'FIELD_2', context)
        
        alg_params = {
            'EXPRESSION': f'attribute( "{field_1}") =  \'{parameters["STRING_1"]}\' OR attribute( "{field_2}") =  \'{parameters["STRING_2"]}\' ',
            'INPUT': parameters[self.INPUT],
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        mm_building_layer = processing.run('native:extractbyexpression', alg_params, context=context, feedback=feedback, is_child_algorithm=True)['OUTPUT']
        
        outputs['JoinAttributesByLocation'] = processing.run("native:joinattributesbylocation", {
                'DISCARD_NONMATCHING' : True,
                'INPUT' : mm_building_layer,
                'JOIN' : parameters['NRD_INPUT'],
                'JOIN_FIELDS' : [],
                'METHOD' : 0,
                'NON_MATCHING' : parameters['OUTPUT_2'],
                'OUTPUT' : parameters['OUTPUT'],
                'PREDICATE' : [0],
                'PREFIX' : ''
            },
            is_child_algorithm=True,
            context=context,
            feedback=feedback)
        results[self.OUTPUT] = outputs['JoinAttributesByLocation']['OUTPUT']
        results[self.OUTPUT_2] = outputs['JoinAttributesByLocation']['NON_MATCHING']
    
        return results

You could also filter features using getFeatures() (with an expression) or native:extractbyattribute (with a single attribute).

2
  • This runs, but returns two layers with no features in. Why would you recommend getFeatures() or native:extractbyattribute? Commented Jan 25, 2024 at 11:32
  • I've updated my answer. There are very good built-in methods, there's no need to manually filter your features. If you still get two layers with no features, can you share a sample of your data? Commented Jan 29, 2024 at 11:29

Your Answer

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.