I am writing a standalone processing algorithm with PyQGIS. The intention of the script is to create an offline basemap that is created only within the extents of objects of one layer. Therefore I am using the native:rasterize
tool in a for loop to create the list rasters
of temporary rasters. This list is eventually used in gdal:merge
to be merged together to one raster. The script runs smoothly until the final step of loading the final raster back to the QGIS project or save it.
Somewhere, I am missing the final link to save the output raster.
So far I am using a combination of defining a output path as parameter QgsProcessingParameterRasterDestination
and use this parameter to set a output layer self.parameterAsOutputLayer
(following this post)
Using this combination QGIS says:
The following layer could not be created: C:/Users/lorenz.beck/AppData/Local/Temp/processing_oPONNl/1b663b5b7c8f4ad28f3477d8e4b9ec93/OUTPUT_RASTER.tif You can check the 'Log Messages Panel' in QGIS main window to find more information about the execution of the algorithm.
And within the 'Log Messages Panel' in QGIS it gives me two warning at the same time:
2025年04月10日T10:10:46 WARNING Cannot open C:/Users/lorenz.beck/AppData/Local/Temp/processing_oPONNl/42c640bc19ee492698f93ab0be05b1e2/OUTPUT.tif ().()
2025年04月10日T10:10:46 WARNING Cannot open C:/Users/lorenz.beck/AppData/Local/Temp/processing_oPONNl/91f5715b037a4c2ea0d2d557d866405d/OUTPUT.tif ().()
Can you help me to successfully save the last raster and load it back into my QGIS project?
I have also run processing.runAndLoadResults("gdal:merge"...)
which adds the raster to my QGIS project. So the output is created, but it doesn't feel like the cleanest way to go.
Here is the code I am using:
from typing import Any, Optional
from qgis.PyQt.QtCore import QVariant
from qgis.core import (
QgsFeatureSink,
QgsProcessing,
QgsProcessingAlgorithm,
QgsProcessingContext,
QgsProcessingException,
QgsProcessingFeedback,
QgsProcessingOutputRasterLayer,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterMapLayer,
QgsProcessingParameterMapTheme,
QgsProcessingParameterNumber,
QgsProcessingParameterRasterDestination,
QgsCoordinateReferenceSystem
)
from qgis import processing
class ExportMapToMultipleRaster(QgsProcessingAlgorithm):
# Constants used to refer to parameters and outputs. They will be
# used when calling the algorithm from another algorithm, or when
# calling from the QGIS console.
INPUT_OBJECTS = "INPUT_OBJECTS"
INPUT_RASTER_LAYER = "INPUT_RASTER_LAYER"
INPUT_MAP_THEME = "INPUT_MAP_THEME"
INPUT_DOUBLE = "INPUT_DOUBLE"
OUTPUT_RASTER = "OUTPUT_RASTER"
def name(self) -> str:
return "exportmaptomultipleraster"
def displayName(self) -> str:
return "Exportiere Karte in mehrere Raster"
def group(self) -> str:
return "Raster"
def groupId(self) -> str:
return "raster"
def shortHelpString(self) -> str:
return "Exportiert einen ein Kartenthema in mehrere Raster basierend auf den Objektausdehnungen eines Polygon-Layers. Wenn kein Kartenthema ausgewählt ist wird die aktuelle Kartendarstellung in ein Raster exportiert"
def initAlgorithm(self, config: Optional[dict[str, Any]] = None):
# We add the input vector feature source. A polygon layer with objects
# that are used to export the map in their extents
self.addParameter(
QgsProcessingParameterFeatureSource(
self.INPUT_OBJECTS,
"Input Polygon Objekte",
[QgsProcessing.SourceType.TypeVectorPolygon],
)
)
# We add a map theme that can be used for an ovelayed visualiziation of the map
self.addParameter(
QgsProcessingParameterMapTheme(
self.INPUT_MAP_THEME,
"Kartenthema",
optional=True
)
)
# We add a double number for setting the resolution of the output raster
self.addParameter(QgsProcessingParameterNumber(
self.INPUT_DOUBLE,
"Pixelgröße (m)",
QgsProcessingParameterNumber.Double,
QVariant(1.0)))
# We add a output raster Layer Destination.
self.addParameter(
QgsProcessingParameterRasterDestination(
self.OUTPUT_RASTER,
"Output Raster"
)
)
def processAlgorithm(
self,
parameters: dict[str, Any],
context: QgsProcessingContext,
feedback: QgsProcessingFeedback,
) -> dict[str, Any]:
"""
Here is where the processing itself takes place.
"""
# Retrieve the feature source
polygon_source = self.parameterAsSource(parameters, self.INPUT_OBJECTS, context)
if polygon_source is None:
raise QgsProcessingException(
self.invalidSourceError(parameters, self.INPUT_OBJECTS)
)
# Retrieve the map layer
map_theme_source = self.parameterAsString(parameters, self.INPUT_MAP_THEME, context)
# User feedback which map style will be exported
if map_theme_source != '':
feedback.pushInfo(f'Das Kartenthema {map_theme_source} wird für das Ausgaberaster verwendet')
else:
feedback.pushInfo('Die aktuelle Kartendarstellung wird für das Ausgaberaster verwendet')
# Retrieve cell size for final raster
cell_size_source = self.parameterAsDouble(parameters, self.INPUT_DOUBLE, context)
# Retrieve output raster
output_raster = self.parameterAsOutputLayer(parameters, self.OUTPUT_RASTER, context)
# User Feedback of CRS and projection if needed
source_crs = polygon_source.sourceCrs().authid()
feedback.pushInfo('CRS is {}'.format(polygon_source.sourceCrs().authid()))
# Compute the number of steps to display within the progress bar and
# get features from source
total = 100.0 / polygon_source.featureCount() if polygon_source.featureCount() else 0
features = polygon_source.getFeatures()
rasters = []
for current, feature in enumerate(features):
# Stop the algorithm if cancel button has been clicked
if feedback.isCanceled():
break
# Get maximum and minimum of each feature's bounding box
xmin = feature.geometry().boundingBox().xMinimum()
xmax = feature.geometry().boundingBox().xMaximum()
ymin = feature.geometry().boundingBox().yMinimum()
ymax = feature.geometry().boundingBox().yMaximum()
# Hereby, we can create the Extent object
# 'xMin, yMax, xMax, yMin, yMax [EPSG:Code]'
extent = f"{xmin},{xmax},{ymin},{ymax} [{source_crs}]"
# If Map theme is not None then take it as input
feedback.pushInfo(f'Map theme is converted to raster')
memory_raster = processing.run(
"native:rasterize",
{'EXTENT':extent,
'EXTENT_BUFFER':0,
'TILE_SIZE':100,
'MAP_UNITS_PER_PIXEL':cell_size_source,
'MAKE_BACKGROUND_TRANSPARENT':False,
'MAP_THEME':map_theme_source,
'LAYERS':None,
'OUTPUT':'TEMPORARY_OUTPUT'},
context=context,
feedback=feedback,
is_child_algorithm=True
)["OUTPUT"]
rasters.append(memory_raster)
# Update the progress bar
feedback.setProgress(int(current * total))
merged_raster = processing.run(
"gdal:merge",
{'INPUT':rasters,
'PCT':False,
'SEPARATE':False,
'NODATA_INPUT':None,
'NODATA_OUTPUT':0,
'OPTIONS':None,
'EXTRA':'',
'DATA_TYPE':1,
'OUTPUT':'TEMPORARY_OUTPUT'},
context=context,
feedback=feedback,
is_child_algorithm=True
)["OUTPUT"]
# Update the progress bar
feedback.setProgress(int(current * total))
results = {}
results[self.OUTPUT_RASTER] = merged_raster
return results
def createInstance(self):
return self.__class__()