7

I'm working with QGIS 3.16 Hannover. I've got a polygon shapefile layer with a 1x1 km grid. Each cell (feature) has 46 attributes corresponding to the percentage of 46 habitat types present in that feature. Values in each attribute range from 0 to 100.

I want to create an atlas showing each feature (cell) and a text box with the values of the attributes (habitats) that are present in that cell, i.e. value > 0.

I have started with this code

case
    when "grassland" > 0
        then 'grassland: '||"grassland"||'%'
    when "grassland" < 0
        then ''
    when "forest" > 0
        then 'forest: '||"forest"||'%'
    when "forest" < 0
        then ''
    (...)
end

Is there a more straightforward way to do it?

15

Manual version

With this option, you manually add a row for each attribute, reusing the same expression. See below how to automatize this for all fieldnames at once.

Create a condition for each attribute manually

You should concatenate a row for each attribute, connecting them using pipes: ||. Better use an if-condition: if >0, then show the value, otherwise simply use an empty sting ''. The expression looks like this:

if ("grassland" > 0, 'grassland: '||"grassland"||'%'  ||  '\n' , '' )  || 
if ("forest" > 0, 'forest: '||"forest"||'%'  ||  '\n' , '' )  || 
if ("steppe" > 0, 'steppe: '||"steppe"||'%'  ||  '\n' , '' )  || 
if ("other" > 0, 'ohter: '||"other"||'%'  ||  '\n' , '' )

I added the text field you asked for above using this exptession and the attribute table below so you can compare the values. The first attribute (grassland) has a value of 0 and is thus not added to the text field: enter image description here

Automatize the expression

If you have dozens of attributes, it can get annoying to copy the expression and each time replace the name of the attribute three times per line. You can automatize this using this expression (to define the order in which the attributes appear, see below):

replace (
    array_to_string (
        array_foreach (
            array_filter( 
                map_akeys( attributes( ) ), 
                @element not in ('id', 'id2')
            ),
            eval (
                replace (
                    'if ("an" > 0, ''an: ''||"an"||''%''  ||  ''\n'' , '''' )  ', 
                    'an', 
                    @element
                )
            )
        )
    ), 
    ',',
    ''
)

Explanation

With map_akeys( attributes( ) ) (line 5) you get an array with all attribute-names. You can filter out some names with array_filter: in line 6, define which attribute names to skip (here: 'id' and 'id2').

Then use array_foreach to iterate: for each of the remaining attribute name, perform the following function.

Define the pattern you used in the version above to create your output as a string (line 10). Instead of the attribute name, insert a generic name (here: an for attribute name), then replace that with the current element (each of the attribute names). Up to now, you have created a string that returns for each element of the array what you created before manually. Use eval to evaluate the string - the string should be converted to a working function. Output is an array of strings. Convert the array to sting (line 2) and delete all the commas (line 1 and 17/18 respectively):

Output with the automatized expression: as you see, all 0-values are skipped, as well as the 2 attributes we defined to skip (id and id2). Only issue is that attributes are sorted in alphbetic order - for other sorting options, see in what follows: enter image description here

Change sort order

Ordering in the same sequence as fields appear in attribute table

See this answer to order the attributes not in alphabetic order as is the default using the function attributes( ), but in the same order as they appear in the attribute table

Custom defined ordering

To sort the entries in any arbitray order, you can use array_prioritize( ) (available since QGIS 3.20) to define manually a custom order.

Ordering depending on the attribute values

To order the results depending on their values (from highest to lowest value), modify the expression this way:

regexp_replace( 
    array_to_string (
        with_variable (
            'output',
            array_foreach (
                array_filter( 
                    map_akeys( attributes( ) ), 
                    @element not in ('fid', 'id')
                ),
                '<sort>' || right (left ('00' || format_number (attribute (@element),1),7),5)  || '</sort>' ||
                eval ( 
                    replace (
                        'if ("el" > 0, ''el: ''||"el"||''%''  ||  ''\n'' , '''' )  ', 
                        'el', 
                        @element
                    )
                )
            ),
            array_sort (@output,0)
        )
    ),
    '(,)|(<sort>.*</sort>)',
    ''
)

The result, showin the attribute table (bottom) and a text field with only those attribute values >0 from the current atlas feature, ordered from highest to smallest attribute value: enter image description here

2
  • Would there be an option to arrange the attributes depending on their value? I.e. Placing first the most represented habitat in the feature and the rest in descending order. Oct 31 '21 at 14:54
  • Added that to my answer.
    – Babel
    Oct 31 '21 at 21:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.