Update: Some 16 months later I know slightly more, so am adding an improved solution.
Loading layers inside the postProcessAlgorithm()
method (as per my original answer) is not best practice nor is it required for this task.
You can achieve this by using a boolean parameter to check if the resulting layers, from either a temporary or file folder, should be loaded on completion. If the parameter evaluates to True
, add the resulting layers to the context
object's temporary layer store and layersToLoadOnCompletion()
, passing a QgsProcessingContext.LayerDetails
object, where you can set the layer's display name. This also allows processing to cleanly handle the loading of the output layers.
from qgis.PyQt.QtCore import QDir
from qgis.core import (QgsProcessing,
QgsProcessingAlgorithm,
QgsProcessingParameterRasterLayer,
QgsProcessingParameterVectorLayer,
QgsProcessingParameterField,
QgsProcessingParameterBoolean,
QgsProcessingParameterFolderDestination,
QgsProcessingMultiStepFeedback,
QgsProcessingContext,
QgsProject,
QgsRasterLayer,
QgsProcessingOutputMultipleLayers)
from pathlib import Path
import processing
import os
class Batch_raster(QgsProcessingAlgorithm):
INPUT = 'INPUT'
FIELDS = 'FIELDS'
OUT_FOLDER = 'OUT_FOLDER'
LOAD_OUTPUTS = 'LOAD_OUTPUTS'
OUTPUT_LAYERS = 'OUTPUT_LAYERS'
def initAlgorithm(self, config=None):
self.addParameter(QgsProcessingParameterRasterLayer('likeraster', 'like raster', defaultValue=None))
self.addParameter(QgsProcessingParameterVectorLayer(self.INPUT, 'vector', defaultValue=None))
self.addParameter(QgsProcessingParameterField(self.FIELDS, 'fields to select', type=0, allowMultiple=True, defaultToAllFields=True, parentLayerParameterName=self.INPUT))
self.addParameter(QgsProcessingParameterBoolean('VERBOSE_LOG', 'Verbose logging', optional=True, defaultValue=False))
self.addParameter(QgsProcessingParameterFolderDestination(self.OUT_FOLDER, 'Output directory')),
self.addParameter(QgsProcessingParameterBoolean(self.LOAD_OUTPUTS, 'Load output layers?', optional=True, defaultValue=True))
self.addOutput(QgsProcessingOutputMultipleLayers(self.OUTPUT_LAYERS, 'Output layers'))
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
results = {}
outputs = {}
output_layers = []
likeraster = self.parameterAsRasterLayer(parameters, 'likeraster', context)
vector = self.parameterAsVectorLayer(parameters, self.INPUT, context)
fields = self.parameterAsFields(parameters, self.FIELDS, context)
out_dir = self.parameterAsString(parameters, self.OUT_FOLDER, context)
load_outputs = self.parameterAsBool(parameters, 'LOAD_OUTPUTS', context)
steps = len(fields)
step = 1
feedback = QgsProcessingMultiStepFeedback(steps, model_feedback)
for index, field in enumerate(fields):
if feedback.isCanceled():
break
feedback.pushInfo(str(field))
if not QDir().mkpath(out_dir):
raise QgsProcessingException('Failed to create output directory')
return {}
out_path = os.path.join(out_dir, f'{field}_rasterized.tif')
# Rasterize (vector to raster)
alg_params = {
'BURN': 0,
'DATA_TYPE': 5,
'EXTENT': likeraster.extent(),
'EXTRA': '',
'FIELD': field,
'INIT': None,
'INPUT': parameters[self.INPUT],
'INVERT': False,
'NODATA': 0,
'OPTIONS': '',
'UNITS': 1,
'HEIGHT':likeraster.rasterUnitsPerPixelY(),
'WIDTH': likeraster.rasterUnitsPerPixelX(),
'OUTPUT': out_path,
}
feedback.setCurrentStep(step)
step+=1
outputs[f'RasterizeVectorToRaster{field}'] = processing.run('gdal:rasterize',
alg_params,
is_child_algorithm=True,
context=context,
feedback=feedback)
results[f'Raster_out{field}'] = outputs[f'RasterizeVectorToRaster{field}']['OUTPUT']
output_layers.append(out_path)
if load_outputs:
file_name = Path(out_path).stem
lyr = QgsRasterLayer(results[f'Raster_out{field}'], file_name, 'gdal')
feedback.pushInfo(repr(lyr.isValid()))
if lyr.isValid():
context.temporaryLayerStore().addMapLayer(lyr)
details = QgsProcessingContext.LayerDetails(lyr.name(), context.project(), lyr.name())
details.forceName = True
context.addLayerToLoadOnCompletion(lyr.id(), details)
results[self.OUTPUT_LAYERS] = output_layers
return results
def name(self):
return 'Batch_rasterize_fields'
def displayName(self):
return 'Batch_rasterize_fields'
def group(self):
return ''
def groupId(self):
return ''
def createInstance(self):
return Batch_raster()
Original answer:
The way I see it, you have a problem with your output destination parameter in that you are adding a QgsProcessingParameterRasterDestination
which is a single raster file destination, but your processing script is creating multiple raster files in the processAlgorithm()
method. So I guess that you are overwriting your output file on each iteration of calls to the rasterize
child algorithm.
I think that processing algorithms which create multiple output files should use a QgsProcessingParameterFolderDestination
parameter as a directory for output files.
I think this probably requires a workaround by re-implementing a postProcessAlgorithm()
method in order to rename and load the output files.
I have modified your script and tested in QGIS versions 3.16 and 3.20. I am not sure if this is the best way to do this but it is at least working. I will be glad if someone knows a better way or can improve my solution.
Modified script:
import os
from qgis.core import (QgsProcessing,
QgsProcessingAlgorithm,
QgsProcessingParameterRasterLayer,
QgsProcessingParameterVectorLayer,
QgsProcessingParameterField,
QgsProcessingParameterBoolean,
QgsProcessingParameterFolderDestination,
QgsProcessingLayerPostProcessorInterface,
QgsProcessingUtils,
QgsProject,
QgsRasterLayer,
)
import processing
class Batch_raster(QgsProcessingAlgorithm):
INPUT = 'INPUT'
FIELDS = 'FIELDS'
OUT_FOLDER = 'OUT_FOLDER'
LOAD_OUTPUTS = 'LOAD_OUTPUTS'
final_layers = {}
load_outputs = True
def initAlgorithm(self, config=None):
self.addParameter(QgsProcessingParameterRasterLayer('likeraster', 'like raster', defaultValue=None))
self.addParameter(QgsProcessingParameterVectorLayer(self.INPUT, 'vector', defaultValue=None))
self.addParameter(QgsProcessingParameterField(self.FIELDS, 'fields to select', type=0, allowMultiple=True, defaultToAllFields=True, parentLayerParameterName=self.INPUT))
self.addParameter(QgsProcessingParameterBoolean('VERBOSE_LOG', 'Verbose logging', optional=True, defaultValue=False))
self.addParameter(QgsProcessingParameterFolderDestination(self.OUT_FOLDER, 'Output directory')),
self.addParameter(QgsProcessingParameterBoolean(self.LOAD_OUTPUTS, 'Load output layers?', optional=True, defaultValue=True))
def processAlgorithm(self, parameters, context, feedback):
# Use a multi-step feedback, so that individual child algorithm progress reports are adjusted for the
# overall progress through the model
results = {}
outputs = {}
likeraster = self.parameterAsRasterLayer(parameters, 'likeraster', context)
vector = self.parameterAsVectorLayer(parameters, self.INPUT, context)
fields = self.parameterAsFields(parameters, self.FIELDS, context)
out_dir = self.parameterAsString(parameters, self.OUT_FOLDER, context)
load = self.parameterAsBool(parameters, 'LOAD_OUTPUTS', context)
if not load:
self.load_outputs = False
for index, field in enumerate(fields):
if feedback.isCanceled():
break
feedback.pushInfo(str(field))
if not 'OUT_FOLDER' in out_dir:
out_path = os.path.join(out_dir, f'{field}_rasterized.tif')
else:
out_path = 'TEMPORARY_OUTPUT'
# Rasterize (vector to raster)
alg_params = {
'BURN': 0,
'DATA_TYPE': 5,
'EXTENT': likeraster.extent(),
'EXTRA': '',
'FIELD': field,
'INIT': None,
'INPUT': parameters[self.INPUT],
'INVERT': False,
'NODATA': 0,
'OPTIONS': '',
'UNITS': 1,
'OUTPUT': f'{field}',
'HEIGHT':likeraster.rasterUnitsPerPixelY(),
'WIDTH': likeraster.rasterUnitsPerPixelX(),
'OUTPUT': out_path,
}
outputs[f'RasterizeVectorToRaster{field}'] = processing.run('gdal:rasterize',
alg_params,
is_child_algorithm=True,
context=context,
feedback=feedback)
results[f'Raster_out{field}'] = outputs[f'RasterizeVectorToRaster{field}']['OUTPUT']
if out_path == 'TEMPORARY_OUTPUT':
self.final_layers[field] = QgsProcessingUtils.mapLayerFromString(outputs[f'RasterizeVectorToRaster{field}']['OUTPUT'], context)
else:
self.final_layers[f'file_{index}'] = QgsProcessingUtils.mapLayerFromString(outputs[f'RasterizeVectorToRaster{field}']['OUTPUT'], context)
pcnt = int(index/len(fields)*100)
feedback.setProgress(pcnt)
return results
######################################################################
def postProcessAlgorithm(self, context, feedback):
if self.load_outputs:
for name, layer in self.final_layers.items():
if layer.name() == 'OUTPUT':
layer.setName(f'{name}_rasterized')
QgsProject.instance().addMapLayer(layer)
else:
self.load_outputs = True
self.final_layers.clear()
return {}
######################################################################
def name(self):
return 'Batch_rasterize_fields'
def displayName(self):
return 'Batch_rasterize_fields'
def group(self):
return ''
def groupId(self):
return ''
def createInstance(self):
return Batch_raster()
Results shown in short screencast below: