QGIS Python Fields – Defining Fields and Adding Features to the Output Layer in a Processing Algorithm in QGIS

fields-attributespyqgisqgis-3

I have two input vector layers (one point and one line layer). I loop through every feature in the input-line-layer and calculate some things (with the help of the point layer).

What I am trying to do now is to generate an output line layer for which I want to have the following six fields:

1. QgsField('id', QVariant.String)
2. QgsField('vto', QVariant.Double)
3. QgsField('vfrom', QVariant.Double)
4. QgsField('priority', QVariant.Int)
5. QgsField('node1', QVariant.String)
6. QgsField('node2', QVariant.String)

Based on those defined fields, I want to add some features to the output layer,
but I do not know how to define the fields (of the output layer) and how to add features to the output layer in QGIS3.

Note: I am using the processing algorithm template for QGIS3 from Underdark: see https://anitagraser.com/2018/03/25/processing-script-template-for-qgis3/

Based on her template, she writes this:

    def processAlgorithm(self, parameters, context, feedback):
        inEdges = self.parameterAsSource(parameters, 
                       self.INPUT_VECTOR_LAYER_EDGES, context)
        inNodes = self.parameterAsSource(parameters, 
                       self.INPUT_VECTOR_LAYER_NODES, context)
        (sink, dest_id) = self.parameterAsSink(parameters, 
                       self.OUTPUT_VECTOR_LAYER_MERGED, context,
                       inEdges.fields(), inEdges.wkbType(), inEdges.sourceCrs())

In the line which starts with (sink, dest_id), underdark has declared an outputlayer that takes the same fields as the inputlayer. But how can I declare an outputlayer with my customized fields (which are not ident with the fields from the inputlayer)?

EDIT:
As suggested from root676, I altered my code a little bit:

# 1. First of all, I defined a variable which holds the definition of my fields
outFields = QgsFields()

# 2. Then, I defined the fields
outFields.append(QgsField('id', QVariant.String))
outFields.append(QgsField('vto', QVariant.Double))
outFields.append(QgsField('vfrom', QVariant.Double))
outFields.append(QgsField('priority', QVariant.Int))
outFields.append(QgsField('node1', QVariant.String))
outFields.append(QgsField('node2', QVariant.String))

# 3. Then I created the output sink with the previously defined fields
(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT_VECTOR_LAYER_MERGED, context,
                                           outFields, inEdges.wkbType(), inEdges.sourceCrs())

# 4. I created a new feature that will be added to the outputlayer after I set its attribute values.
newEdge = QgsFeature()

# 5. The ERROR happens when I try to set an attribute value like this:
newEdge['id'] = edgeIdTrimmed

The error I am getting is as followed:

Traceback (most recent call last):
File "", line 143, in processAlgorithm
KeyError: 'id'

EDIT: I solved the problem by initializing the new QgsFeature with my defined Fields variable. See below:

newEdge = QgsFeature(outFields)

Best Answer

Here is an example of a possible base-layout of your processAlgorithm() method (just a quick wrap-up of the basic field related code you were asking about - has to be embedded into your context):

def processAlgorithm(self, parameters, context, feedback):
    #get your input layers
    inEdges = self.parameterAsSource(parameters, 
                   self.INPUT_VECTOR_LAYER_EDGES, context)
    inNodes = self.parameterAsSource(parameters, 
                   self.INPUT_VECTOR_LAYER_NODES, context)

    #define your fields
    your_fields = QgsFields()

    #append fields
    your_fields.append(QgsField('id', QVariant.String))
    your_fields.append(QgsField('vto', QVariant.Double))
    your_fields.append(QgsField('vfrom', QVariant.Double))
    your_fields.append(QgsField('priority', QVariant.Int))
    your_fields.append(QgsField('node1', QVariant.String))
    your_fields.append(QgsField('node2', QVariant.String))

    #create the output sink
    (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT_VECTOR_LAYER_MERGED, context, your_fields, inEdges.wkbType(), inEdges.sourceCrs())

    #do some calculations with inEdges and inNodes

    #somewhere in your while/for loops create a new feature object
    feat = QgsFeature()

    #set your fields of the feature
    feat.setFields(your_fields)

    #set the attribute values like this 
    feat['vto'] = #some_vto_value

    #set geometry of feature
    feat.setGeometry(geom)

    #add feature to sink
    sink.addFeature(feat, QgsFeatureSink.FastInsert)        

    #return feature sink
    results = {}
    results[self.OUTPUT_VECTOR_LAYER_MERGED] = dest_id
    return results

The following steps are necessary to output a sink with custom fields (see corresponding code comments):

  1. get your input layers
  2. define your fields as QgsFields object
  3. append the corresponding QgsField objects to the QgsFields
  4. create the output QgsFeatureSink using the fields from the variable your_fields
  5. do your calculations with the inEdges and inNodes layers
  6. create features using your_fields, set their attributes and geometry in the calculation process
  7. add the features to the sink
  8. return the sink as new output layer.
Related Question