patternpythonMinor
Composing QGIS expression based on a QGIS layer tree
Viewed 0 times
expressionlayercomposingbasedtreeqgis
Problem
I am developing a plugin for a GIS (Geographic Information Systems) software which uses Python. I have been using Python for over a year (on-off) and have the following code which works. It basically reads the names of files loaded into the GIS software to compose a QGIS expression for every group of files. Finally it deletes the list, ready for the next group.
The script is quite chunky and uses repetition. I am wondering how would one improve it to make it more compact and run more efficiently?
```
root = QgsProject.instance().layerTreeRoot()
policy_group = root.findGroup('Names')
formula_score4 = []
formula_score3a = []
formula_score3 = []
formula_score2a = []
formula_score2 = []
formula_score1 = []
for group in policy_group.children():
for layer in group.children():
score4 = '"' + layer.layerName() + '_Score' + '"' + ' = 4 OR '
formula_score4.append(score4)
for layer in group.children():
score3a = 'coalesce(' + layer.layerName() + ' = 3, 0.00)*3 OR '
formula_score3a.append(score3a)
for layer in group.children():
score3 = '"' + layer.layerName() + '_Score' + '"' + ' = 3 OR '
formula_score3.append(score3)
for layer in group.children():
score2a = 'coalesce(' + layer.layerName() + ' = 2, 0.00)*2 OR '
formula_score2a.append(score2a)
for layer in group.children():
score2 = '"' + layer.layerName() + '_Score' + '"' + ' = 2 OR '
formula_score2.append(score2)
for layer in group.children():
score1 = '"' + layer.layerName() + '_Score' + '"' + ' = 1 OR '
formula_score1.append(score1)
formula1 = "CASE WHEN " + "".join(str(x) for x in formula_score4) + "".join(str(x) for x in formula_score3a) + ">=9 THEN 4 " + "WHEN " + "".join(str(x) for x in formula_score3) + "".join(str(x) for x in formula_score2a) + ">=6 THEN 3 " + "WHEN " + "".join(str(x) for x in formula_score2) + "THEN 2 " + "WHEN " + "".join(str(x) for x in formula_score1) + "THEN 1 ELSE 1 EN
The script is quite chunky and uses repetition. I am wondering how would one improve it to make it more compact and run more efficiently?
```
root = QgsProject.instance().layerTreeRoot()
policy_group = root.findGroup('Names')
formula_score4 = []
formula_score3a = []
formula_score3 = []
formula_score2a = []
formula_score2 = []
formula_score1 = []
for group in policy_group.children():
for layer in group.children():
score4 = '"' + layer.layerName() + '_Score' + '"' + ' = 4 OR '
formula_score4.append(score4)
for layer in group.children():
score3a = 'coalesce(' + layer.layerName() + ' = 3, 0.00)*3 OR '
formula_score3a.append(score3a)
for layer in group.children():
score3 = '"' + layer.layerName() + '_Score' + '"' + ' = 3 OR '
formula_score3.append(score3)
for layer in group.children():
score2a = 'coalesce(' + layer.layerName() + ' = 2, 0.00)*2 OR '
formula_score2a.append(score2a)
for layer in group.children():
score2 = '"' + layer.layerName() + '_Score' + '"' + ' = 2 OR '
formula_score2.append(score2)
for layer in group.children():
score1 = '"' + layer.layerName() + '_Score' + '"' + ' = 1 OR '
formula_score1.append(score1)
formula1 = "CASE WHEN " + "".join(str(x) for x in formula_score4) + "".join(str(x) for x in formula_score3a) + ">=9 THEN 4 " + "WHEN " + "".join(str(x) for x in formula_score3) + "".join(str(x) for x in formula_score2a) + ">=6 THEN 3 " + "WHEN " + "".join(str(x) for x in formula_score2) + "THEN 2 " + "WHEN " + "".join(str(x) for x in formula_score1) + "THEN 1 ELSE 1 EN
Solution
You do quite a lot or repetition. Using
Doing these inside a
And if you make a list to iterate over you can do this all in two comprehensions.
I also think you should make a comprehension to get the layer name from your
Finally you will want to build
As all the strings used to format the data are constant, you can use
And should result in something like:
Finally to make the main part of your code,
The output of this function only uses
You also used the wrong delimiter for the
To improve on the above I'd change
This will allow you to easily change both for all your different functions.
This will also change the
This unfortunately makes the
but give us an excuse to use implicit string concatenation which can make it much easier to read.
str.format you should be able to make your code smaller by using a comprehensions or map.Doing these inside a
''.join can allow you to reduce a lot of your code.And if you make a list to iterate over you can do this all in two comprehensions.
I also think you should make a comprehension to get the layer name from your
group.children().Finally you will want to build
formula1 from this which can be done with another string with format.As all the strings used to format the data are constant, you can use
UPPER_SNAKE_CASE variable names to say it's a constant.And should result in something like:
SCORE_FORMATS = [
'"{}_Score" = 1 OR ',
'"{}_Score" = 2 OR ',
'"{}_Score" = 3 OR ',
'"{}_Score" = 4 OR ',
'coalesce({} = 2, 0.00)*2 OR ',
'coalesce({} = 3, 0.00)*3 OR ']
FORMULA1 = "CASE WHEN {3}{5}>=9 THEN 4 WHEN {2}{4}>=6 THEN 3 WHEN {1}THEN 2 WHEN {0}THEN 1 ELSE 1 END"Finally to make the main part of your code,
formula1 to final_formula, you can use the following as I explained above:for group in policy_group.children():
group_children = [layer.layerName() for layer in group.children()]
formulas = [
''.join(map(formula_format.format, group_children))
for formula_format in SCORE_FORMATS
]
formula1 = FORMULA1.format(*formulas)
formula2 = formula1.replace("OR >=9 THEN 4 ", ">=9 THEN 4 ")
formula3 = formula2.replace("OR >=6 THEN 3 ", ">=6 THEN 3 ")
formula4 = formula3.replace("OR THEN 2 ", "THEN 2 ")
final_formula = formula4.replace("OR THEN 1 ELSE 1 END", "THEN 1 ELSE 1 END")The output of this function only uses
final_formula where I was originally unsure if you were using all the formulas.You also used the wrong delimiter for the
coalesce.To improve on the above I'd change
SCORE_FORMATS to be tuples of (delimiter, format).This will allow you to easily change both for all your different functions.
This will also change the
''.join(map(...)) to use your delimiter and will remove the need for all the replace calls.This unfortunately makes the
FORMULA a bit longer,but give us an excuse to use implicit string concatenation which can make it much easier to read.
SCORE_FORMATS = [
(' OR ', '"{}_Score" = 1'),
(' OR ', '"{}_Score" = 2'),
(' OR ', '"{}_Score" = 3'),
(' OR ', '"{}_Score" = 4'),
(' + ', 'coalesce({} = 2, 0.00)*2'),
(' + ', 'coalesce({} = 3, 0.00)*3')]
FORMULA = ('CASE WHEN {3} OR {5} >= 9 THEN 4 '
'WHEN {2} OR {4} >= 6 THEN 3 '
'WHEN {1} THEN 2 '
'WHEN {0} THEN 1 '
'ELSE 1 END')
for group in policy_group.children():
group_children = [layer.layerName() for layer in group.children()]
formula = FORMULA.format(*[
delimiter.join(map(formula_format.format, group_children))
for delimiter, formula_format in SCORE_FORMATS
])Code Snippets
SCORE_FORMATS = [
'"{}_Score" = 1 OR ',
'"{}_Score" = 2 OR ',
'"{}_Score" = 3 OR ',
'"{}_Score" = 4 OR ',
'coalesce({} = 2, 0.00)*2 OR ',
'coalesce({} = 3, 0.00)*3 OR ']
FORMULA1 = "CASE WHEN {3}{5}>=9 THEN 4 WHEN {2}{4}>=6 THEN 3 WHEN {1}THEN 2 WHEN {0}THEN 1 ELSE 1 END"for group in policy_group.children():
group_children = [layer.layerName() for layer in group.children()]
formulas = [
''.join(map(formula_format.format, group_children))
for formula_format in SCORE_FORMATS
]
formula1 = FORMULA1.format(*formulas)
formula2 = formula1.replace("OR >=9 THEN 4 ", ">=9 THEN 4 ")
formula3 = formula2.replace("OR >=6 THEN 3 ", ">=6 THEN 3 ")
formula4 = formula3.replace("OR THEN 2 ", "THEN 2 ")
final_formula = formula4.replace("OR THEN 1 ELSE 1 END", "THEN 1 ELSE 1 END")SCORE_FORMATS = [
(' OR ', '"{}_Score" = 1'),
(' OR ', '"{}_Score" = 2'),
(' OR ', '"{}_Score" = 3'),
(' OR ', '"{}_Score" = 4'),
(' + ', 'coalesce({} = 2, 0.00)*2'),
(' + ', 'coalesce({} = 3, 0.00)*3')]
FORMULA = ('CASE WHEN {3} OR {5} >= 9 THEN 4 '
'WHEN {2} OR {4} >= 6 THEN 3 '
'WHEN {1} THEN 2 '
'WHEN {0} THEN 1 '
'ELSE 1 END')
for group in policy_group.children():
group_children = [layer.layerName() for layer in group.children()]
formula = FORMULA.format(*[
delimiter.join(map(formula_format.format, group_children))
for delimiter, formula_format in SCORE_FORMATS
])Context
StackExchange Code Review Q#127989, answer score: 8
Revisions (0)
No revisions yet.