[GIS] Store Result of a Processing Algorithm as a Layer in QGIS Python Script

pyqgispyqgis-3pythonqgisqgis-processing

I have converted a graphic model into a script. I want to expend this script and would like to store the result of the Reprojected Algorithm as a layer so as to get the output layers extent.

I am using QgsVectorLayer class to create a vector layer from the result of the algorithm. When I am checking if the layer is valid, the layer is invalid. How can I convert a result of the processing algorithm as a valid vector layer, from which I can extract the extent with .extent() method.

Code Snippet:

        # Reproject layer
        alg_params = {
            'INPUT': parameters['areaboundary'],
            'OPERATION': '',
            'TARGET_CRS': QgsCoordinateReferenceSystem('EPSG:4326'),
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['ReprojectLayer'] = processing.run('native:reprojectlayer', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

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

        feedback.pushInfo(str(outputs['ReprojectLayer']['OUTPUT']))
        area_layer = QgsVectorLayer(outputs['ReprojectLayer']['OUTPUT'], 'Reprojected', 'memory')
        if not area_layer.isValid():
            feedback.pushInfo('not valid')
        else:
            QgsProject.instance().addMapLayer(area_layer)

Full Script:


from qgis.core import QgsProcessing, QgsVectorLayer
from qgis.core import QgsProcessingAlgorithm
from qgis.core import QgsProcessingMultiStepFeedback
from qgis.core import QgsProcessingParameterVectorLayer
from qgis.core import QgsProcessingParameterFeatureSink
from qgis.core import QgsCoordinateReferenceSystem
import processing


class DownloadSoil(QgsProcessingAlgorithm):

    def initAlgorithm(self, config=None):
        self.addParameter(QgsProcessingParameterVectorLayer('areaboundary', 'Area Boundary', types=[QgsProcessing.TypeVectorPolygon], defaultValue=None))
        self.addParameter(QgsProcessingParameterFeatureSink('Swapped', 'Swapped', 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(3, model_feedback)
        results = {}
        outputs = {}

        # Reproject layer
        alg_params = {
            'INPUT': parameters['areaboundary'],
            'OPERATION': '',
            'TARGET_CRS': QgsCoordinateReferenceSystem('EPSG:4326'),
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['ReprojectLayer'] = processing.run('native:reprojectlayer', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

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

        feedback.pushInfo(str(outputs['ReprojectLayer']['OUTPUT']))
        area_layer = QgsVectorLayer(outputs['ReprojectLayer']['OUTPUT'], 'Reprojected', 'memory')
        if not area_layer.isValid():
            feedback.pushInfo('not valid')
        else:
            QgsProject.instance().addMapLayer(area_layer)



        feedback.pushInfo(str(area_layer.sourceName()))
        xmin = area_layer.extent().xMinimum()
        ymin = area_layer.extent().yMinimum()
        xmax = area_layer.extent().xMaximum()
        ymax = area_layer.extent().yMaximum()


        feedback.pushInfo(str(ymin))

        requestURL = 'https://sdmdataaccess.sc.egov.usda.gov/Spatial/SDMWGS84GEOGRAPHIC.wfs?SERVICE=WFS&VERSION=1.1.0&REQUEST=GetFeature&TYPENAME=MapunitPoly&SRSNAME=EPSG:4326&BBOX=' + str(xmin) + ',' + str(ymin) + ',' + str(xmax) + ',' + str(ymax)
        feedback.pushInfo(requestURL)


        # Download file
        alg_params = {
            'URL': requestURL,
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['DownloadFile'] = processing.run('native:filedownloader', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

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

        # Swap X and Y coordinates
        alg_params = {
            'INPUT': outputs['DownloadFile']['OUTPUT'],
            'OUTPUT': parameters['Swapped']
        }
        outputs['SwapXAndYCoordinates'] = processing.run('native:swapxy', alg_params, context=context, feedback=feedback, is_child_algorithm=True)
        results['Swapped'] = outputs['SwapXAndYCoordinates']['OUTPUT']
        return results

    def name(self):
        return 'Download Soil'

    def displayName(self):
        return 'Download Soil'

    def group(self):
        return 'Test Models'

    def groupId(self):
        return 'Test Models'

    def createInstance(self):
        return DownloadSoil()

Best Answer

native:reprojectlayer is executed as child algorithm. PyQGIS states in https://github.com/qgis/QGIS/blob/c5a56d6f83e08b77967326add5ad215e3a0c948e/python/plugins/processing/tools/general.py#L110 that:

for child algorithms, we disable to default post-processing step where layer ownership is transferred from the context to the caller. In this case, we NEED the ownership to remain with the context, so that further steps in the algorithm have guaranteed access to the layer.

This means that the result of the native:reprojectlayer is a string with the layer id and the ownership of this layer is of the QgsProcessingContext in use during algorithm execution. To get access to the layer, one possible way would be to take back its ownership using QgsProcessingContenxt.takeResultLayer(%layer_id%) The short example hereafter takes back the ownership of the layer and pushes the information about the extent to the log of the algorithm:

def initAlgorithm(self, config=None):
    # No differences from Someone191's code

def processAlgorithm(self, parameters, context, feedback):
    s_alg_id = "native:reprojectlayer"

    di_input = {
        "INPUT": parameters[self.AREA_BND],
        "TARGET_CRS": QgsCoordinateReferenceSystem("EPSG:4326"),
        "OUTPUT": parameters["Swapped"]
    }
    di_out = processing.run(s_alg_id, di_input, context=context, feedback=feedback, is_child_algorithm=True)
    # This is a string with the id of the memory layer # 
    lyr_out_str = di_out["OUTPUT"]
    # This is how to take back the ownership of the layer from the context # 
    lyr_out = context.takeResultLayer(lyr_out_str)
    if lyr_out.isValid():
        feedback.pushInfo(lyr_out.extent().asWktPolygon())

    return {"OUTPUT": lyr_out_str, "DI_OUT": di_out}

A sketch of the output is presented in the following picture: enter image description here

A more extensive example of how to use the takeResultLayer() is given directly in the implementation of PyQGIS is e.g., [%qgis_install_dir%\python\plugins\processing\core\Processing.py:151-176]. I hope this helps you!

Related Question