2

I am writing a QGIS python script. In the script, I am using QgsProcessingParameterRasterDestination class to ask the user where do they want to save the output raster.

For any other QGIS processing operation, you can pass it input and output parameters and it will take care of writing the output and returning it, but I am using QGIS: GDAL rasterize with attribute algorithm. The problem with this one is that it does not have any outputs. It takes a raster and modifies it, without returning anything.

        # Rasterize (overwrite with attribute)
        alg_params = {
            "ADD": False,
            "EXTRA": "",
            "FIELD": parameters[self.field],
            "INPUT": parameters[self.inputVector],
            "INPUT_RASTER": C,
        }
        outputs["RasterizeOverwriteWithAttribute"] = processing.run(
            "gdal:rasterize_over",
            alg_params,
            context=context,
            feedback=feedback,
            is_child_algorithm=True,
        )

I do not want to modify the original raster, To overcome this issue what I would do is copy the input to the output location and then run the tool. What I am struggling with is

  1. how to find the location to copy raster to, parameterAsString is not working.
  2. return this raster as QGIS would return the output of any other tool

I hope this will clarify or else I can send my actual code. Searching the pyQGIS documentation I only found examples where algorithms were able to write to the parameter. Is anyone aware of a method to feed an existing raster to a destination parameter?

1 Answer 1

4
+25

The way I would approach this is to define a source raster and an output raster destination (which can be either a temporary or a file output, then copy the input raster using gdal translate, passing the output raster destination parameter as input. The output from the gdal translate algorithm is what you then pass as the input to the rasterize (overwrite with attribute) algorithm. The only catch is that if the user specifies a temporary output, then the output layer name will be 'Converted' (inherited from the gdal translate output) so you can use a QgsProcessingLayerPostProcessorInterface sub-class to rename the output layer if necessary. I guess this is something of a workaround (and there may be a better way), but it is still a working solution and, to be honest, the most straightforward one I can think of. You can see a full example below:

from qgis.core import QgsProcessing
from qgis.core import QgsProcessingAlgorithm
from qgis.core import QgsProcessingParameterVectorLayer
from qgis.core import QgsProcessingParameterField
from qgis.core import QgsProcessingParameterRasterLayer
from qgis.core import QgsProcessingParameterRasterDestination
from qgis.core import QgsProcessingLayerPostProcessorInterface
from qgis.core import QgsRasterLayer
import processing


class Group_output_layers(QgsProcessingAlgorithm):
    inputVector = 'input_vector'
    field = 'field'
    inputRaster = 'source_raster'
    outputRaster = 'output_raster'
    
    def name(self):
        return 'overwrite_raster_copy'

    def displayName(self):
        return 'Overwrite raster copy'

    def group(self):
        return 'Models'

    def groupId(self):
        return 'Models'

    def createInstance(self):
        return Group_output_layers()
    
    def initAlgorithm(self, config=None):
        self.addParameter(QgsProcessingParameterVectorLayer('input_vector', 'Input vector'))
        self.addParameter(QgsProcessingParameterField('field', 'Field to use as burn in value', parentLayerParameterName=self.inputVector, type=0))
        self.addParameter(QgsProcessingParameterRasterLayer('source_raster', 'Source raster'))
        self.addParameter(QgsProcessingParameterRasterDestination('output_raster', 'Output raster'))

    def processAlgorithm(self, parameters, context, feedback):
        results = {}
        outputs = {}
        
        sourceraster = self.parameterAsRasterLayer(parameters, 'source_raster', context)
        
        # Copy source raster with gdal translate
        alg1_params = {'INPUT':parameters['source_raster'],
                        'TARGET_CRS':None,
                        'NODATA':None,
                        'COPY_SUBDATASETS':False,
                        'OPTIONS':'',
                        'EXTRA':'',
                        'DATA_TYPE':0,
                        'OUTPUT':parameters['output_raster']}
        
        outputs['copied_raster']= processing.run("gdal:translate",
                alg1_params,
                is_child_algorithm=True,
                context=context,
                feedback=feedback)
        
        # Rasterize (overwrite with attribute)
        alg2_params = {'INPUT':parameters[self.inputVector],
                        'INPUT_RASTER':outputs['copied_raster']['OUTPUT'],
                        'FIELD':parameters[self.field],
                        'ADD':False,
                        'EXTRA':''}
                        
        outputs['rasterized_over'] = processing.run("gdal:rasterize_over",
                alg2_params,
                is_child_algorithm=True,
                context=context,
                feedback=feedback)
        
        results['OUTPUT']=outputs['rasterized_over']['OUTPUT']
        
        # Use a layer post processor to rename output layer (if memory layer)
        if context.willLoadLayerOnCompletion(results['OUTPUT']):
            context.layerToLoadOnCompletionDetails(results['OUTPUT']).setPostProcessor(MyLayerPostProcessor.create())
        
        return results
        
        
# Courtesy of Nyall Dawson: https://gist.github.com/nyalldawson/26c091dd48b4f8bf56f172efe22cf75f
class MyLayerPostProcessor(QgsProcessingLayerPostProcessorInterface):

    instance = None

    def postProcessLayer(self, layer, context, feedback):  # pylint: disable=unused-argument
        if not isinstance(layer, QgsRasterLayer):
            return
        """
        If output layer is a temporary memory layer, it will have the name 'Converted',
        otherwise it will have the user-specified name from the output file location.
        So we check the layer name before setting it's name.
        """
        if layer.name() == 'Converted':
            layer.setName('Rasterized_over')
        

    # Hack to work around sip bug!
    @staticmethod
    def create() -> 'MyLayerPostProcessor':
        """
        Returns a new instance of the post processor, keeping a reference to the sip
        wrapper so that sip doesn't get confused with the Python subclass and call
        the base wrapper implementation instead... ahhh sip, you wonderful piece of sip
        """
        MyLayerPostProcessor.instance = MyLayerPostProcessor()
        return MyLayerPostProcessor.instance

Screencast demo:

enter image description here

The code for the MyLayerPostProcessor() class I adapted from an example by Nyall Dawson in a GitHub gist (which I can't link to as it seems to have been deleted).

1
  • Thanks for detailed answer. I am doing this right now to handle this but the problem with this method is that QGIS store this raster in memory in step 1 but does not refresh layer after step 2. Hence you lend in this awkward situation where layer saved on the disk is correct, but outdated in QGIS project. See this for details github.com/qgis/QGIS/issues/45729 Nov 21, 2021 at 16:17

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.