2

I have a QGIS model that I want to modify with an IF personalised script.

Basically one of the inputs is optional, but if it's empty then the model is throwing some errors down the line. Hence the script would aim to check IF the input is empty, in which case would create and empty dummy shapefile, and use it on the rest of the calculations.

Below is the script that I've tested outside the graphic modeler, following the suggestions here QGIS Modeler if/else condition, custom script for Modeler

##Example=name
##DOB=optional raster
##output=output vector

if DOB is None:
    fake_DOB = 'C:/Temp/fake_DOB.shp'
    layerFields = QgsFields()
    layerFields.append(QgsField('DOB', QVariant.Double))
    output = QgsVectorFileWriter(fake_DOB, 'UTF-8', layerFields, QgsWkbTypes.Point,\
    QgsCoordinateReferenceSystem(), 'ESRI Shapefile')
    feat = QgsFeature()
    feat.setAttributes(['',0])
    output.addFeature(feat)
else:
    output=DOB

However, it sort of works outside the graphic modeller environment, but as soon as I try to add it to the list of algorithms an error pops stating:

NameError: name 'QgsFields' is not defined

any ideas?


UPDATE: Since it was pointed out that I was using the wrong structure for QGIS 3.x I've been trying to 'translate' the code using the not-very-beginners-friendly template... unsuccessfully.

I'm now getting an error that I can't seem to solve. Ideas?

NameError: name 'QVariant' is not defined

from qgis.PyQt.QtCore import QCoreApplication
from qgis.core import (QgsProcessing,
                       QgsFeatureSink,
                       QgsProcessingException,
                       QgsProcessingAlgorithm,
                       QgsProcessingParameterFeatureSource,
                       QgsProcessingParameterFeatureSink,
                       QgsField,
                       QgsFields,
                       QgsProcessingParameterField,
                       QgsProcessingParameterVectorLayer)
from qgis import processing


class dummy_DOB(QgsProcessingAlgorithm):

    INPUT = 'DOB'
    OUTPUT = 'OUTPUT'

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

    def createInstance(self):
        return dummy_DOB()

    def name(self):
        return 'dummy_DOB'

    def displayName(self):
        return self.tr('dummy_DOB')

    def group(self):
        return self.tr('Scripts')

    def groupId(self):
        return 'Scripts'

    def shortHelpString(self):
        return self.tr("Creates a dummy DOB if empty")

    def initAlgorithm(self, config=None):
        self.addParameter(QgsProcessingParameterVectorLayer(self.INPUT, 'DOB', optional=True, types=[QgsProcessing.TypeVectorPoint], defaultValue=None))

        self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT,self.tr('Output DOB layer')))

    def processAlgorithm(self, parameters, context, feedback):
        """
        Here is where the processing itself takes place.
        """
        source = self.parameterAsSource(
            parameters,
            self.INPUT,
            context
        )
        if source is None:
            fake_DOB = 'C:/Temp/fake_DOB.shp'
            layerFields = QgsFields()
            layerFields.append(QgsField('DOB', QVariant.Double))
            output = QgsVectorFileWriter(fake_DOB, 'UTF-8', layerFields, QgsWkbTypes.Point,\
            QgsCoordinateReferenceSystem(), 'ESRI Shapefile')
            feat = QgsFeature()
            feat.setAttributes(['',0])
            output.addFeature(feat)
        else:
            output=source
        (sink, dest_id) = self.parameterAsSink(
            parameters,
            self.OUTPUT,
            context,
            source.fields(),
            source.wkbType(),
            source.sourceCrs()
        )
        if sink is None:
            raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT))
        return {self.OUTPUT: dest_id}

UPDATE:

I kept importing processes and applications.

from qgis.PyQt.QtCore import QCoreApplication
from qgis.core import (QgsProcessing,
                       QgsFeatureSink,
                       QgsProcessingException,
                       QgsProcessingAlgorithm,
                       QgsProcessingParameterFeatureSource,
                       QgsProcessingParameterFeatureSink,
                       QgsField,
                       QgsFields,
                       QgsWkbTypes,
                       QgsFeature,
                       QgsProcessingParameterField,
                       QgsVectorFileWriter,
                       QgsProcessingParameterVectorLayer,
                       QgsCoordinateReferenceSystem)
from qgis import processing
from PyQt5.QtCore import *

I have finally a different type of error:

File "", line 78, in processAlgorithm

AttributeError: 'NoneType' object has no attribute 'fields'

I think is related with the fact that I haven't stored any outputs in the sink. TBH I don't know how to do it, I just kept that part from the Template, I'm trying hard to make this work but I'm far from being an expert.

source = self.parameterAsSource(
    parameters,
    self.INPUT,
    context
)
if source is None:
    fake_DOB = 'C:/Temp/fake_DOB.shp'
    layerFields = QgsFields()
    layerFields.append(QgsField('DOB', QVariant.Double))
    output = QgsVectorFileWriter(fake_DOB, 'UTF-8', layerFields, QgsWkbTypes.Point,\
    QgsCoordinateReferenceSystem(), 'ESRI Shapefile')
    feat = QgsFeature()
    feat.setAttributes(['',0])
    output.addFeature(feat)
else:
    output=source

(sink, dest_id) = self.parameterAsSink(
    parameters,
    self.OUTPUT,
    context,
    source.fields(), ##line of the error
    source.wkbType(),
    source.sourceCrs()
)
if sink is None:
    raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT))
return {self.OUTPUT: dest_id}
  • 2
    Which QGIS version are you using? That script only works for QGIS 2.x but you tagged QGIS 3. – Joseph Jan 16 at 12:00
  • 1
    AHA! I'm using 3.10 – Ame ZL Jan 16 at 12:01
  • 1
    This may help with your new error regarding QVariant: gis.stackexchange.com/questions/95877/…. The comments from @MrPurple state a standalone process in QGIS3.X should include the line from PyQt4.QtCore import * – smiller Jan 16 at 14:38
  • @smiller I added PyQt5 (as it was suggested by QGIS itself after I tried with PyQt4). I think I've imported all is required now. Falling into a different issue. – Ame ZL Jan 16 at 15:17
2

Try using something like the following where it is based on the new processing syntax. Create a Vector Layer input parameter in your modeler, set it so that it is not mandatory (i.e. it will be optional) and then use this as the input to the script. The script should check if the input is empty where it will create a dummy shapefile otherwise would use the layer selected:

from qgis.processing import alg
from qgis.core import QgsFeature, QgsWkbTypes, QgsField, QgsFields, QgsPointXY, QgsGeometry, \
QgsCoordinateReferenceSystem, QgsProcessingMultiStepFeedback, QgsVectorFileWriter, QgsVectorLayer
from PyQt5.QtCore import QVariant

@alg(name="Test script", label=alg.tr("Test script"), group="examplescripts", group_label=alg.tr("Example Scripts"))
@alg.input(type=alg.SOURCE, name="INPUT", label="Input layer", optional=True)
@alg.input(type=alg.VECTOR_LAYER_DEST, name="OUTPUT", label="Output layer")
def testalg(instance, parameters, context, feedback, inputs):
    """
    Description goes here. (Don't delete this! Removing this comment will cause errors.)
    """        
    feedback = QgsProcessingMultiStepFeedback(1, feedback)
    if parameters['INPUT'] is None:
        fake_DOB = 'C:/Temp/fake_DOB.shp'
        layerFields = QgsFields()
        layerFields.append(QgsField('DOB', QVariant.Double))
        output = QgsVectorFileWriter(fake_DOB, 'UTF-8', layerFields, QgsWkbTypes.Point,\
        QgsCoordinateReferenceSystem(), 'ESRI Shapefile')
        feat = QgsFeature()
        feat.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(0.0,0.0)))
        feat.setAttributes(['',0])
        output.addFeature(feat)
        parameters['OUTPUT'] = QgsVectorLayer(fake_DOB, 'fake_DOB', 'ogr')
    else:
        parameters['OUTPUT'] = parameters['INPUT']

    return {'OUTPUT': parameters['OUTPUT']}
  • Hi, I tried your script, is not sending an error, and it does create the file in the temp folder, at the moment it doesn't appear to do anything else, so I wanted to try it in the model, but I'm not being able to add it as an algorithm in the processing tool. any ideas? Ta. – Ame ZL Jan 16 at 15:41
  • 1
    @AmeZL - The script checks if the input is empty or not, in either case it should generate an output which can be used in another tool. For example, if you choose the buffer tool, use the output of the script as the input and see if you get a result. – Joseph Jan 16 at 15:45
  • that's perfect, and it's exactly what I'm looking for!. However, I'm trying Processing toolbox > add script to toolbox and is not showing up, so atm I'm unable to add it into the model as in[gis.stackexchange.com/questions/228224/… ?? – Ame ZL Jan 16 at 16:03
  • @AmeZL - Did you ensure the script was saved in your QGIS processing directory (e.g. /AppData/Roaming/QGIS/QGIS3/profiles/default/processing/scripts/) – Joseph Jan 16 at 16:07
  • 1
    Hi, So I deleted all other scripts in the same folder, they were faulty and messing up. This finally allowed me to add the script to my model. And the script works, although the model keeps failing downstream, but that is another issue. This thread is closed, as @Joseph solution really address and solve the issue I raised originally. Thanks – Ame ZL Jan 16 at 17:13

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.