3

How can I query the symbology of a layer so that I have as many symbols as all possible combinations of "chrono1", "chrono2", "chrono3"? I'm looking for an automatic way to find all possible combinations of an array and turn them into symbols.

I could write the rules by hand but I would like to find an automatic way. Is it possible?

example

The combinations will be many more than those visible in the example image. Therefore I need to find an easier way to categorize the symbols.

Taras
  • 32,823
  • 4
  • 66
  • 137
ddomizia
  • 648
  • 4
  • 13

2 Answers2

7

You could use the Categorized renderer with this expression "chrono1"+"chrono2"+"chrono3" in the value field.

enter image description here

This will create all already existing combinations (so not all possible combinations) but if you edit your data and create new combination you could add them by either reclassifying (but that will change all symbology) or by clicking the "+" button and manually setting the new combination

J.R
  • 16,090
  • 1
  • 19
  • 52
7

In addition to @J.R's answer, you can adapt this answer from SO together with this answer from GIS SE to get a list of all possible combinations using Python/PyQGIS and set up a categorized renderer with all theoretically possible categories automagically.

Just copy paste the script to your Python editor in QGIS and change the name-strings in the fields-list (5th line) to your needs. Then select your layer and hit run. Depending on your number of features, number of fields and possible combinations it can take a while.

### Imports and Settings
import itertools as it
from random import randrange

layer = iface.activeLayer() # the layer you want to work with fields = ['chrono_1','chrono_2','chrono_3','chrono_4'] # the fieldnames you want to consider

This is the part to generate a list of all possible combinations

fieldsValuesDict = {} possibleCombinations = []

create a dictionary with fieldnames as keys

for field in fields: fieldsValuesDict[field] = []

fill the dictionary with lists of all values in the layer

for feat in layer.getFeatures(): for field in fields: if feat[field] not in (None,NULL): fieldsValuesDict[field].append(feat[field]) else: fieldsValuesDict[field].append('')

remove duplicate values from the dictionaries values-list

for k, v in fieldsValuesDict.items(): fieldsValuesDict[k] = list(dict.fromkeys(v))

#print(fieldsValuesDict)

create a list of all possible combinations of all value-lists of all keys in the dictionary

allNames = sorted(fieldsValuesDict) combinations = it.product(*(fieldsValuesDict[Name] for Name in allNames)) for item in list(combinations): possibleCombinations.append(''.join(item))

possibleCombinations.sort() #print(possibleCombinations)

This is the part to setup the categorized renderer to all possible combinations

categories = [] for combination in possibleCombinations: # initialize the default symbol for this geometry type symbol = QgsSymbol.defaultSymbol(layer.geometryType())

# configure a symbol layer
layer_style = {}
layer_style['color'] = '%d, %d, %d' % (randrange(0, 256), randrange(0, 256), randrange(0, 256))
layer_style['outline'] = '#000000'
symbol_layer = QgsSimpleFillSymbolLayer.create(layer_style)

# replace default symbol layer with the configured one
if symbol_layer is not None:
    symbol.changeSymbolLayer(0, symbol_layer)

# create renderer object
category = QgsRendererCategory(combination, symbol, str(combination))
# entry for the list of category items
categories.append(category)

create renderer object

mycat = '+'.join(fields) renderer = QgsCategorizedSymbolRenderer(mycat, categories)

assign the created renderer to the layer

if renderer is not None: layer.setRenderer(renderer)

refresh

layer.triggerRepaint()

Demo:

enter image description here

MrXsquared
  • 34,292
  • 21
  • 67
  • 117
  • 1
    @MrXsqared this is exactly what I need. However, when I run the code, the console gives me an error: exec(open('/var/folders/s5/k0t_jqpx3dddt02r2n_llc9w0000gn/T/tmpnzbej5vb.py'.encode('utf-8')).read()) Traceback (most recent call last): File "/Applications/QGIS.app/Contents/MacOS/../Resources/python/code.py", line 90, in runcode exec(code, self.locals) File "", line 1, in File "", line 23, in TypeError: sequence item 0: expected str instance, QVariant found – ddomizia Oct 14 '21 at 07:25
  • 1
    @MrXsquared I can see your progress :) – Taras Oct 14 '21 at 07:34
  • 1
    @ddomizia thanks for the hint, just fixed that. The error occured if a field is None or NULL. Just added if feat[field] not in (None,NULL): at line 19 to prevent the tuples from containing these values. @Taras thanks :) – MrXsquared Oct 14 '21 at 07:50
  • 1
    Thanks @MrXsquared! Now the error is no longer there. However I only have one combination as a result – ddomizia Oct 14 '21 at 08:00
  • 1
    @MrXsquared yes, the only difference is that I also have pairs of letters: for example: chrono_1 "A"; chrono_2 "PA"; chrono_3 "FA"; chrono_4 "I". Can this be the problem? I have tried closing and reopening QGIS but the situation does not change. – ddomizia Oct 14 '21 at 08:13
  • @MrXsquared Of course, I have changed the names of the fields. The only result I see is a "CFAPSI", i.e. the array of values within the fields "chrono_1" (C) + "chrono_2" (FA) + "chrono_3" (PS) + "chrono_4" (I). – ddomizia Oct 14 '21 at 08:22
  • Certainly, I leave you a link with the data set. https://we.tl/t-vguc9fB9IK – ddomizia Oct 14 '21 at 08:34
  • @ddomizia For now I have implemented to handle NULL values as empty string ''. Check if that is already what you are looking for. If not, please update your question and describe how your desired result would look like and especially how to handle the NULL values. – MrXsquared Oct 14 '21 at 17:43