1

Hi I'm writing a new processing script for QGIS 3.4.5 and am stuck at the last step where I have to add the features to the newly created layer, then add the layer to the map.

For more context, this script is supposed to create distance buffer polygons from a point feature on my map. However when I run this, I get "Feature could not be written to output_df2fd0f5_18af_42b1_b07f_64e113493b86" and the output that comes out of this is an empty point layer. Here's my code:

# -*- coding: utf-8 -*-

"""
***************************************************************************
*                                                                         *
*   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.                                   *
*                                                                         *
***************************************************************************
"""

from math import *
from PyQt5.QtCore import QCoreApplication, QVariant
from qgis.core import (QgsProcessing,
                       QgsFeatureSink,
                       QgsFeature,
                       QgsVectorLayer,
                       QgsGeometry,
                       QgsPointXY,
                       QgsField,
                       QgsFields,
                       QgsProject,
                       QgsProcessingException,
                       QgsProcessingAlgorithm,
                       QgsProcessingParameterFeatureSource,
                       QgsProcessingParameterFeatureSink,
                       QgsProcessingParameterField,
                       )
import processing


class DistanceBuffer(QgsProcessingAlgorithm):
    """
    Dissolve algorithm that dissolves features based on selected
    attribute and summarizes the selected field by cumputing the
    sum of dissolved features.
    """
    INPUT = 'INPUT'
    OUTPUT = 'OUTPUT'

    def tr(self, string):
        """
        Returns a translatable string with the self.tr() function.
        """
        return QCoreApplication.translate('Processing', string)

    def createInstance(self):
        return DistanceBuffer()

    def name(self):
        """
        Returns the algorithm name, used for identifying the algorithm. This
        string should be fixed for the algorithm, and must not be localised.
        The name should be unique within each provider. Names should contain
        lowercase alphanumeric characters only and no spaces or other
        formatting characters.
        """
        return 'distance_buffer'

    def displayName(self):
        """
        Returns the translated algorithm name, which should be used for any
        user-visible display of the algorithm name.
        """
        return self.tr('Distance Buffer')

    def group(self):
        """
        Returns the name of the group this algorithm belongs to. This string
        should be localised.
        """
        return self.tr('scripts')

    def groupId(self):
        """
        Returns the unique ID of the group this algorithm belongs to. This
        string should be fixed for the algorithm, and must not be localised.
        The group id should be unique within each provider. Group id should
        contain lowercase alphanumeric characters only and no spaces or other
        formatting characters.
        """
        return 'scripts'

    def shortHelpString(self):
        """
        Returns a localised short helper string for the algorithm. This string
        should provide a basic description about what the algorithm does and the
        parameters and outputs associated with it..
        """
        return self.tr("Create distance buffers and style.")

    def initAlgorithm(self, config=None):
        """
        Here we define the inputs and output of the algorithm, along
        with some other properties.
        """
        # We add the input vector features source. It can have any kind of
        # geometry.
        self.addParameter(
            QgsProcessingParameterFeatureSource(
                self.INPUT,
                self.tr('Input layer'),
                [QgsProcessing.TypeVectorPoint]
            )
        )

        # We add a feature sink in which to store our processed features (this
        # usually takes the form of a newly created vector layer when the
        # algorithm is run in QGIS).
        self.addParameter(
            QgsProcessingParameterFeatureSink(
                self.OUTPUT,
                self.tr('Output layer'),
                type = QgsProcessing.TypeVectorPolygon
            )
        )

    def processAlgorithm(self, parameters, context, feedback):
        """
        Here is where the processing itself takes place.
        """
        source = self.parameterAsSource(
            parameters,
            self.INPUT,
            context
        )

        fields = QgsFields()
        fields.append(QgsField("Distance", QVariant.Int))
        fields.append(QgsField("Label", QVariant.String))

        (sink, dest_id) = self.parameterAsSink(
            parameters,
            self.OUTPUT,
            context, fields, source.wkbType(), source.sourceCrs())


        # Create a dictionary to hold the unique values from the 
        # dissolve_field and the sum of the values from the sum_field

        selection = source.getFeatures()
        for select in selection:
            geom = select.geometry()
            start_x = geom.asPoint().x()
            start_y = geom.asPoint().y()
            sides = 64
            radius = 6378137.0 # meters
            distance = [250, 500, 1000, 2000, 3000, 4000, 5000, 10000, 15000, 20000, 25000, 30000, 40000, 50000]
            feedback.pushInfo('Creating layer and adding fields.')
            # Create new layer.
            vl = QgsVectorLayer("Polygon", "Distance Buffers", "memory")
            # Add fields.
            pr = vl.dataProvider()
            pr.addAttributes(fields)
            vl.updateFields()
            vl.startEditing()
            for i in range(len(distance)):
                feedback.pushInfo('Creating feature.')
                points = []
                dist = distance[i]
                degrees = 0
                while degrees <= 360:
                    degrees = degrees + 360 / sides
                    start_lon = start_x * pi / 180
                    start_lat = start_y * pi / 180
                    bearing = degrees * pi / 180
                    end_lat = asin((sin(start_lat) * cos(dist / radius)) + (cos(start_lat) * sin(dist / radius) * cos(bearing)))
                    end_lon = start_lon + atan2(sin(bearing) * sin(dist / radius) * cos(start_lat),
                                                cos(dist / radius) - (sin(start_lat) * sin(end_lat)))
                    points.append(QgsPointXY(end_lon * 180 / pi, end_lat * 180 / pi))
                    feat_name = str(distance[i])
                    if distance[i] < 1000:
                        label = str(distance[i]) + "m"
                    else:
                        label = str(distance[i]/1000) + "km"
                feedback.pushInfo('Adding feature to layer.')
                # Add calculated features.
                feat = QgsFeature()
                geometry = QgsGeometry.fromPolygonXY([points])
                feat.setGeometry(geometry)
                feat.setAttributes([feat_name,label])
                sink.addFeature(feat, QgsFeatureSink.FastInsert)
            vl.commitChanges()
            vl.updateExtents()
            #QgsProject.instance().addMapLayer(vl)

        return {self.OUTPUT: dest_id}

I may be wrong, but I suspect the bit that I'm not getting is the feature sink. Where am I going wrong here?

  • you should add the features into the sink: sink.addFeature(fet, QgsFeatureSink.FastInsert) and not into the layer. The layer will be defined in the initAlgorithm, something like: self.addParameter(QgsProcessingParameterFeatureSink( self.OUTPUT, self.tr('Output layer'), type=QgsProcessing.TypeVectorPoint)) – eurojam Mar 1 at 6:15
  • @eurojam I've added the "sink.addFeature(feat, QgsFeatureSink.FastInsert)" line but now I'm getting a new error message as shown in my edited post. Also, since the output that I'm looking for is a polygon layer, should the last bit to your comment be: self.addParameter(QgsProcessingParameterFeatureSink( self.OUTPUT, self.tr('Output layer'), type=QgsProcessing.TypeVectorPolygon))? – Park Sang Jin Mar 1 at 9:37
  • Of course, you should change it to a vector layer. may be the error will be gone...hope so;-) – eurojam Mar 1 at 10:17
1

I've imported:

from qgis.core import QgsWkbTypes

changed this part of my code:

(sink, dest_id) = self.parameterAsSink(
            parameters,
            self.OUTPUT,
            context, fields, source.wkbType(), source.sourceCrs())

to:

(sink, dest_id) = self.parameterAsSink(
            parameters,
            self.OUTPUT,
            context, fields, QgsWkbTypes.Polygon, source.sourceCrs())

The output was creating a point layer (same as the input source), but I was trying to add polygon features to it, hence the error.

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.