Skip to content

Commit 580ecaf

Browse files
authored
Merge pull request #7396 from luipir/rastercalculator_model_fix
[processing] Rebirth RasterCalculator in Modeler. Fixes #19302
2 parents 42aea6c + 30eddf5 commit 580ecaf

File tree

5 files changed

+135
-38
lines changed

5 files changed

+135
-38
lines changed

python/plugins/processing/algs/qgis/RasterCalculator.py

+100-29
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@
4040
QgsProcessingParameterRasterLayer,
4141
QgsProcessingOutputRasterLayer,
4242
QgsProcessingParameterString,
43-
QgsCoordinateTransform)
43+
QgsCoordinateTransform,
44+
QgsMapLayer)
45+
from qgis.PyQt.QtCore import QObject
4446
from qgis.analysis import QgsRasterCalculator, QgsRasterCalculatorEntry
4547

4648

@@ -77,23 +79,6 @@ def type(self):
7779
def clone(self):
7880
return ParameterRasterCalculatorExpression(self.name(), self.description(), self.multiLine())
7981

80-
def evaluateForModeler(self, value, model):
81-
for i in list(model.inputs.values()):
82-
param = i.param
83-
if isinstance(param, QgsProcessingParameterRasterLayer):
84-
new = "{}@".format(os.path.basename(param.value))
85-
old = "{}@".format(param.name())
86-
value = value.replace(old, new)
87-
88-
for alg in list(model.algs.values()):
89-
for out in alg.algorithm.outputs:
90-
if isinstance(out, QgsProcessingOutputRasterLayer):
91-
if out.value:
92-
new = "{}@".format(os.path.basename(out.value))
93-
old = "{}:{}@".format(alg.modeler_name, out.name)
94-
value = value.replace(old, new)
95-
return value
96-
9782
self.addParameter(ParameterRasterCalculatorExpression(self.EXPRESSION, self.tr('Expression'),
9883
multiLine=True))
9984
self.addParameter(QgsProcessingParameterMultipleLayers(self.LAYERS,
@@ -122,17 +107,17 @@ def processAlgorithm(self, parameters, context, feedback):
122107

123108
layersDict = {}
124109
if layers:
125-
layersDict = {os.path.basename(lyr.source().split(".")[0]): lyr for lyr in layers}
110+
layersDict = {lyr.source(): lyr for lyr in layers}
126111

127112
crs = self.parameterAsCrs(parameters, self.CRS, context)
128-
if not layers and not crs.isValid():
129-
raise QgsProcessingException(self.tr("No reference layer selected nor CRS provided"))
130-
131-
if not crs.isValid() and layers:
132-
crs = list(layersDict.values())[0].crs()
113+
if crs is None or not crs.isValid():
114+
if not layers:
115+
raise QgsProcessingException(self.tr("No reference layer selected nor CRS provided"))
116+
else:
117+
crs = list(layersDict.values())[0].crs()
133118

134119
bbox = self.parameterAsExtent(parameters, self.EXTENT, context)
135-
if not layers and bbox.isNull():
120+
if bbox.isNull() and not layers:
136121
raise QgsProcessingException(self.tr("No reference layer selected nor extent box provided"))
137122

138123
if not bbox.isNull():
@@ -145,7 +130,7 @@ def processAlgorithm(self, parameters, context, feedback):
145130
bbox = QgsProcessingUtils.combineLayerExtents(layers, crs)
146131

147132
cellsize = self.parameterAsDouble(parameters, self.CELLSIZE, context)
148-
if not layers and cellsize == 0:
133+
if cellsize == 0 and not layers:
149134
raise QgsProcessingException(self.tr("No reference layer selected nor cellsize value provided"))
150135

151136
def _cellsize(layer):
@@ -157,15 +142,23 @@ def _cellsize(layer):
157142
if cellsize == 0:
158143
cellsize = min([_cellsize(lyr) for lyr in layersDict.values()])
159144

145+
# check for layers available in the model
146+
layersDictCopy = layersDict.copy() # need a shallow copy because next calls invalidate iterator
147+
for lyr in layersDictCopy.values():
148+
expression = self.mappedNameToLayer(lyr, expression, layersDict, context)
149+
150+
# check for layers available in the project
160151
for lyr in QgsProcessingUtils.compatibleRasterLayers(context.project()):
161-
name = lyr.name()
162-
if (name + "@") in expression:
163-
layersDict[name] = lyr
152+
expression = self.mappedNameToLayer(lyr, expression, layersDict, context)
164153

154+
# create the list of layers to be passed as inputs to RasterCalculaltor
155+
# at this phase expression has been modified to match available layers
156+
# in the current scope
165157
entries = []
166158
for name, lyr in layersDict.items():
167159
for n in range(lyr.bandCount()):
168160
ref = '{:s}@{:d}'.format(name, n + 1)
161+
169162
if ref in expression:
170163
entry = QgsRasterCalculatorEntry()
171164
entry.ref = ref
@@ -178,6 +171,7 @@ def _cellsize(layer):
178171
width = math.floor((bbox.xMaximum() - bbox.xMinimum()) / cellsize)
179172
height = math.floor((bbox.yMaximum() - bbox.yMinimum()) / cellsize)
180173
driverName = GdalUtils.getFormatShortNameFromFilename(output)
174+
181175
calc = QgsRasterCalculator(expression,
182176
output,
183177
driverName,
@@ -213,3 +207,80 @@ def processBeforeAddingToModeler(self, algorithm, model):
213207
values.append(ValueFromOutput(alg.modeler_name, out.name))
214208

215209
algorithm.params[self.LAYERS] = values
210+
211+
def mappedNameToLayer(self, lyr, expression, layersDict, context):
212+
'''Try to identify if a real layer is mapped in the expression with a symbolic name.'''
213+
214+
nameToMap = lyr.source()
215+
216+
# check if nameToMap is a file
217+
# TODO: what about URI eg for a COG?
218+
if os.path.isfile(nameToMap):
219+
# get only the name without extension and path of the file
220+
nameToMap = os.path.splitext(os.path.basename(nameToMap))[0]
221+
222+
# check for layers directly added in the expression
223+
if (nameToMap + "@") in expression:
224+
layersDict[nameToMap] = lyr
225+
226+
# get "algorithm_inputs" scope of the expressionContext related
227+
# with mapped variables
228+
indexOfScope = context.expressionContext().indexOfScope("algorithm_inputs")
229+
if indexOfScope >= 0:
230+
expContextAlgInputsScope = context.expressionContext().scope(indexOfScope)
231+
232+
# check for the layers that are mapped as input in a model
233+
# to do this check in the latest scope all passed variables
234+
# to look for a variable that is a layer or a string filename ç
235+
# to a layer
236+
varDescription = None
237+
for varName in expContextAlgInputsScope.variableNames():
238+
239+
layerInContext = expContextAlgInputsScope.variable(varName)
240+
241+
if not isinstance(layerInContext, str) and not isinstance(layerInContext, QgsMapLayer):
242+
continue
243+
244+
if isinstance(layerInContext, QgsMapLayer) and nameToMap not in layerInContext.source():
245+
continue
246+
247+
varDescription = expContextAlgInputsScope.description(varName)
248+
249+
# because there can be variable with None or "" description
250+
# then skip them
251+
if not varDescription:
252+
continue
253+
254+
# check if it's description starts with Output as in:
255+
# Output 'Output' from algorithm 'calc1'
256+
# as set in https://github.com/qgis/QGIS/blob/master/src/core/processing/models/qgsprocessingmodelalgorithm.cpp#L516
257+
# but var in expression is called simply
258+
# 'Output' from algorithm 'calc1'
259+
260+
# get the translatin string to use to parse the description
261+
# HAVE to use the same translated string as in
262+
# https://github.com/qgis/QGIS/blob/master/src/core/processing/models/qgsprocessingmodelalgorithm.cpp#L516
263+
translatedDesc = self.tr("Output '%1' from algorithm '%2'")
264+
elementZero = translatedDesc.split(" ")[0] # For english the string result should be "Output"
265+
266+
elements = varDescription.split(" ")
267+
if len(elements) > 1 and elements[0] == elementZero:
268+
# remove heading QObject.tr"Output ") string. Note adding a space at the end of elementZero!
269+
varDescription = varDescription[len(elementZero) + 1:]
270+
271+
# check if cleaned varDescription is present in the expression
272+
# if not skip it
273+
if (varDescription + "@") not in expression:
274+
continue
275+
276+
# !!!found!!! => substitute in expression
277+
# and add in the list of layers that will be passed to raster calculator
278+
nameToMap = varName
279+
new = "{}@".format(nameToMap)
280+
old = "{}@".format(varDescription)
281+
expression = expression.replace(old, new)
282+
283+
layersDict[nameToMap] = lyr
284+
285+
# need return the modified expression because it's not a reference
286+
return expression

python/plugins/processing/algs/qgis/ui/RasterCalculatorWidgets.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -214,10 +214,11 @@ def createWidget(self):
214214
return QLineEdit()
215215
else:
216216
layers = self.dialog.getAvailableValuesOfType([QgsProcessingParameterRasterLayer], [QgsProcessingOutputRasterLayer])
217-
options = {self.dialog.resolveValueDescription(lyr): "{}@1".format(lyr) for lyr in layers}
217+
options = {self.dialog.resolveValueDescription(lyr): "{}@1".format(self.dialog.resolveValueDescription(lyr)) for lyr in layers}
218218
return self._panel(options)
219219

220220
def refresh(self):
221+
# TODO: check if avoid code duplication with self.createWidget
221222
layers = QgsProcessingUtils.compatibleRasterLayers(QgsProject.instance())
222223
options = {}
223224
for lyr in layers:
Binary file not shown.

src/core/processing/models/qgsprocessingmodelalgorithm.cpp

+4-1
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ QVariantMap QgsProcessingModelAlgorithm::processAlgorithm( const QVariantMap &pa
273273
QgsExpressionContext expContext = baseContext;
274274
expContext << QgsExpressionContextUtils::processingAlgorithmScope( child.algorithm(), parameters, context )
275275
<< createExpressionContextScopeForChildAlgorithm( childId, context, parameters, childResults );
276+
context.setExpressionContext( expContext );
276277

277278
QVariantMap childParams = parametersForChildAlgorithm( child, parameters, childResults, expContext );
278279
if ( feedback )
@@ -530,6 +531,7 @@ QMap<QString, QgsProcessingModelAlgorithm::VariableDefinition> QgsProcessingMode
530531
if ( !layer )
531532
layer = QgsProcessingUtils::mapLayerFromString( value.toString(), context );
532533

534+
variables.insert( safeName( name ), VariableDefinition( QVariant::fromValue( layer ), source, description ) );
533535
variables.insert( safeName( QStringLiteral( "%1_minx" ).arg( name ) ), VariableDefinition( layer ? layer->extent().xMinimum() : QVariant(), source, QObject::tr( "Minimum X of %1" ).arg( description ) ) );
534536
variables.insert( safeName( QStringLiteral( "%1_miny" ).arg( name ) ), VariableDefinition( layer ? layer->extent().yMinimum() : QVariant(), source, QObject::tr( "Minimum Y of %1" ).arg( description ) ) );
535537
variables.insert( safeName( QStringLiteral( "%1_maxx" ).arg( name ) ), VariableDefinition( layer ? layer->extent().xMaximum() : QVariant(), source, QObject::tr( "Maximum X of %1" ).arg( description ) ) );
@@ -595,6 +597,7 @@ QMap<QString, QgsProcessingModelAlgorithm::VariableDefinition> QgsProcessingMode
595597
featureSource = vl;
596598
}
597599

600+
variables.insert( safeName( name ), VariableDefinition( value, source, description ) );
598601
variables.insert( safeName( QStringLiteral( "%1_minx" ).arg( name ) ), VariableDefinition( featureSource ? featureSource->sourceExtent().xMinimum() : QVariant(), source, QObject::tr( "Minimum X of %1" ).arg( description ) ) );
599602
variables.insert( safeName( QStringLiteral( "%1_miny" ).arg( name ) ), VariableDefinition( featureSource ? featureSource->sourceExtent().yMinimum() : QVariant(), source, QObject::tr( "Minimum Y of %1" ).arg( description ) ) );
600603
variables.insert( safeName( QStringLiteral( "%1_maxx" ).arg( name ) ), VariableDefinition( featureSource ? featureSource->sourceExtent().xMaximum() : QVariant(), source, QObject::tr( "Maximum X of %1" ).arg( description ) ) );
@@ -606,7 +609,7 @@ QMap<QString, QgsProcessingModelAlgorithm::VariableDefinition> QgsProcessingMode
606609

607610
QgsExpressionContextScope *QgsProcessingModelAlgorithm::createExpressionContextScopeForChildAlgorithm( const QString &childId, QgsProcessingContext &context, const QVariantMap &modelParameters, const QVariantMap &results ) const
608611
{
609-
std::unique_ptr< QgsExpressionContextScope > scope( new QgsExpressionContextScope() );
612+
std::unique_ptr< QgsExpressionContextScope > scope( new QgsExpressionContextScope( QStringLiteral( "algorithm_inputs" ) ) );
610613
QMap< QString, QgsProcessingModelAlgorithm::VariableDefinition> variables = variablesForChildAlgorithm( childId, context, modelParameters, results );
611614
QMap< QString, QgsProcessingModelAlgorithm::VariableDefinition>::const_iterator varIt = variables.constBegin();
612615
for ( ; varIt != variables.constEnd(); ++varIt )

0 commit comments

Comments
 (0)