40
40
QgsProcessingParameterRasterLayer ,
41
41
QgsProcessingOutputRasterLayer ,
42
42
QgsProcessingParameterString ,
43
- QgsCoordinateTransform )
43
+ QgsCoordinateTransform ,
44
+ QgsMapLayer )
44
45
from qgis .analysis import QgsRasterCalculator , QgsRasterCalculatorEntry
45
46
46
47
@@ -77,6 +78,7 @@ def type(self):
77
78
def clone (self ):
78
79
return ParameterRasterCalculatorExpression (self .name (), self .description (), self .multiLine ())
79
80
81
+ # TODO: remove this unused method?
80
82
def evaluateForModeler (self , value , model ):
81
83
for i in list (model .inputs .values ()):
82
84
param = i .param
@@ -125,15 +127,16 @@ def processAlgorithm(self, parameters, context, feedback):
125
127
layersDict = {os .path .basename (lyr .source ().split ("." )[0 ]): lyr for lyr in layers }
126
128
127
129
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 ()
133
135
134
136
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" ))
137
140
138
141
if not bbox .isNull ():
139
142
bboxCrs = self .parameterAsExtentCrs (parameters , self .EXTENT , context )
@@ -145,7 +148,7 @@ def processAlgorithm(self, parameters, context, feedback):
145
148
bbox = QgsProcessingUtils .combineLayerExtents (layers , crs )
146
149
147
150
cellsize = self .parameterAsDouble (parameters , self .CELLSIZE , context )
148
- if not layers and cellsize == 0 :
151
+ if cellsize == 0 and not layers :
149
152
raise QgsProcessingException (self .tr ("No reference layer selected nor cellsize value provided" ))
150
153
151
154
def _cellsize (layer ):
@@ -157,15 +160,23 @@ def _cellsize(layer):
157
160
if cellsize == 0 :
158
161
cellsize = min ([_cellsize (lyr ) for lyr in layersDict .values ()])
159
162
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
160
169
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 )
164
171
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
165
175
entries = []
166
176
for name , lyr in layersDict .items ():
167
177
for n in range (lyr .bandCount ()):
168
178
ref = '{:s}@{:d}' .format (name , n + 1 )
179
+
169
180
if ref in expression :
170
181
entry = QgsRasterCalculatorEntry ()
171
182
entry .ref = ref
@@ -178,6 +189,7 @@ def _cellsize(layer):
178
189
width = math .floor ((bbox .xMaximum () - bbox .xMinimum ()) / cellsize )
179
190
height = math .floor ((bbox .yMaximum () - bbox .yMinimum ()) / cellsize )
180
191
driverName = GdalUtils .getFormatShortNameFromFilename (output )
192
+
181
193
calc = QgsRasterCalculator (expression ,
182
194
output ,
183
195
driverName ,
@@ -213,3 +225,69 @@ def processBeforeAddingToModeler(self, algorithm, model):
213
225
values .append (ValueFromOutput (alg .modeler_name , out .name ))
214
226
215
227
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