Skip to content

Commit 5a3b319

Browse files
committed
change expression to use layers in the current execution scope
1 parent 5ebe5d6 commit 5a3b319

File tree

1 file changed

+90
-12
lines changed

1 file changed

+90
-12
lines changed

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

+90-12
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@
4040
QgsProcessingParameterRasterLayer,
4141
QgsProcessingOutputRasterLayer,
4242
QgsProcessingParameterString,
43-
QgsCoordinateTransform)
43+
QgsCoordinateTransform,
44+
QgsMapLayer)
4445
from qgis.analysis import QgsRasterCalculator, QgsRasterCalculatorEntry
4546

4647

@@ -77,6 +78,7 @@ def type(self):
7778
def clone(self):
7879
return ParameterRasterCalculatorExpression(self.name(), self.description(), self.multiLine())
7980

81+
# TODO: remove this unused method?
8082
def evaluateForModeler(self, value, model):
8183
for i in list(model.inputs.values()):
8284
param = i.param
@@ -125,15 +127,16 @@ def processAlgorithm(self, parameters, context, feedback):
125127
layersDict = {os.path.basename(lyr.source().split(".")[0]): lyr for lyr in layers}
126128

127129
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()
130+
if not crs or not crs.isValid():
131+
if not layers:
132+
raise QgsProcessingException(self.tr("No reference layer selected nor CRS provided"))
133+
else:
134+
crs = list(layersDict.values())[0].crs()
133135

134136
bbox = self.parameterAsExtent(parameters, self.EXTENT, context)
135-
if not layers and bbox.isNull():
136-
raise QgsProcessingException(self.tr("No reference layer selected nor extent box provided"))
137+
if not bbox or bbox.isNull():
138+
if not layers:
139+
raise QgsProcessingException(self.tr("No reference layer selected nor extent box provided"))
137140

138141
if not bbox.isNull():
139142
bboxCrs = self.parameterAsExtentCrs(parameters, self.EXTENT, context)
@@ -145,7 +148,7 @@ def processAlgorithm(self, parameters, context, feedback):
145148
bbox = QgsProcessingUtils.combineLayerExtents(layers, crs)
146149

147150
cellsize = self.parameterAsDouble(parameters, self.CELLSIZE, context)
148-
if not layers and cellsize == 0:
151+
if cellsize == 0 and not layers:
149152
raise QgsProcessingException(self.tr("No reference layer selected nor cellsize value provided"))
150153

151154
def _cellsize(layer):
@@ -157,15 +160,23 @@ def _cellsize(layer):
157160
if cellsize == 0:
158161
cellsize = min([_cellsize(lyr) for lyr in layersDict.values()])
159162

163+
# check for layers available in the model
164+
layersDictCopy = layersDict.copy() # need a shallow copy because next calls invalidate iterator
165+
for lyr in layersDictCopy.values():
166+
expression = self.mappedNameToLayer(lyr, expression, layersDict, context)
167+
168+
# check for layers available in the project
160169
for lyr in QgsProcessingUtils.compatibleRasterLayers(context.project()):
161-
name = lyr.name()
162-
if (name + "@") in expression:
163-
layersDict[name] = lyr
170+
expression = self.mappedNameToLayer(lyr, expression, layersDict, context)
164171

172+
# create the list of layers to be passed as inputs to RasterCalculaltor
173+
# at this phase expression has been modified to match available layers
174+
# in the current scope
165175
entries = []
166176
for name, lyr in layersDict.items():
167177
for n in range(lyr.bandCount()):
168178
ref = '{:s}@{:d}'.format(name, n + 1)
179+
169180
if ref in expression:
170181
entry = QgsRasterCalculatorEntry()
171182
entry.ref = ref
@@ -178,6 +189,7 @@ def _cellsize(layer):
178189
width = math.floor((bbox.xMaximum() - bbox.xMinimum()) / cellsize)
179190
height = math.floor((bbox.yMaximum() - bbox.yMinimum()) / cellsize)
180191
driverName = GdalUtils.getFormatShortNameFromFilename(output)
192+
181193
calc = QgsRasterCalculator(expression,
182194
output,
183195
driverName,
@@ -213,3 +225,69 @@ def processBeforeAddingToModeler(self, algorithm, model):
213225
values.append(ValueFromOutput(alg.modeler_name, out.name))
214226

215227
algorithm.params[self.LAYERS] = values
228+
229+
def mappedNameToLayer(self, lyr, expression, layersDict, context):
230+
'''Try to identify if a real layer is mapped in the expression with a symbolic name.'''
231+
232+
nameToMap = lyr.name()
233+
234+
# get last scope of the expressionContext because should be that related
235+
# with mapped variables
236+
# The scope name should be "algorithm_inputs"
237+
expContextLastScope = context.expressionContext().lastScope()
238+
239+
# check for layers directly added in the expression
240+
if (nameToMap + "@") in expression:
241+
layersDict[nameToMap] = lyr
242+
243+
# check for the layers that are mapped as input in a model
244+
# to do this check in the latest scope all passed variables
245+
# to look for a variable that is a layer or a string filename ç
246+
# to a layer
247+
varId = None
248+
varDescription = None
249+
250+
for varName in expContextLastScope.variableNames():
251+
252+
layer = expContextLastScope.variable(varName)
253+
254+
if not isinstance(layer, str) and not isinstance(layer, QgsMapLayer):
255+
continue
256+
257+
if isinstance(layer, QgsMapLayer) and nameToMap not in layer.source():
258+
continue
259+
260+
varId = varName
261+
varDescription = expContextLastScope.description(varName)
262+
263+
# because there can be variable with None or "" description
264+
# then skip them
265+
if not varDescription:
266+
continue
267+
268+
# check if it's description starts with Output as in:
269+
# Output 'Output' from algorithm 'calc1'
270+
# as set in https://github.com/qgis/QGIS/blob/master/src/core/processing/models/qgsprocessingmodelalgorithm.cpp#L516
271+
# but var in expression is called simply
272+
# 'Output' from algorithm 'calc1'
273+
elements = varDescription.split(" ")
274+
if len(elements) > 1 and elements[0] == "Output":
275+
# remove heading "Output " string
276+
varDescription = varDescription[7:]
277+
278+
# check if cleaned varDescription is present in the expression
279+
# if not skip it
280+
if (varDescription + "@") not in expression:
281+
continue
282+
283+
# !!!found!!! => substitute in expression
284+
# and add in the list of layers that will be passed to raster calculator
285+
nameToMap = varName
286+
new = "{}@".format(nameToMap)
287+
old = "{}@".format(varDescription)
288+
expression = expression.replace(old, new)
289+
290+
layersDict[nameToMap] = lyr
291+
292+
# need return the modified expression because it's not a reference
293+
return expression

0 commit comments

Comments
 (0)