I have a polygon layer in QGIS 3.32.2 which I want to clean for later analysis. There should be no polygons that share any area at all, but they would touch quite frequently. However, the polygon layer I have to use has some errors in it so that sometimes there are equal polygons overlapping or they overlap by just a bit.

I want to annotate each feature with all types of overlap it has with other features/polygons from the layer. Disjoint and touches would be ok while everything else needs to be filtered out. Since I want/need to work non-destructively, I would annotate each feature and then filter the layer based on that. I chose to do this using the field calculator and got the following function:

--touch and intersect
WHEN array_to_string(overlay_touches('field_calc_test_6eea42a9_d4df_4074_9eac_bc1b465fed54', $geometry)) <> '' THEN 'touches_and_intersects'
WHEN array_to_string(overlay_equals('field_calc_test_6eea42a9_d4df_4074_9eac_bc1b465fed54', $geometry)) <> '' THEN 'equals'
WHEN array_to_string(overlay_touches('field_calc_test_6eea42a9_d4df_4074_9eac_bc1b465fed54', $geometry)) <> '' THEN 'touches'
WHEN array_to_string(overlay_disjoint('field_calc_test_6eea42a9_d4df_4074_9eac_bc1b465fed54', $geometry)) <> '' THEN 'disjoint'
WHEN array_to_string(overlay_within('field_calc_test_6eea42a9_d4df_4074_9eac_bc1b465fed54', $geometry)) <> ''  THEN 'within'
WHEN array_to_string(overlay_contains('field_calc_test_6eea42a9_d4df_4074_9eac_bc1b465fed54', $geometry)) <> '' THEN 'contains'
WHEN array_to_string(overlay_intersects('field_calc_test_6eea42a9_d4df_4074_9eac_bc1b465fed54', $geometry))   <> '' THEN 'intersect'
ELSE 'no_overlap'

I ran the code on a smaller layer that I created to have all kinds of overlaps/disjoint. I got results, but they were just not right. When I then edited it so that e.g. a disjoint also cannot intersect or touch other features (using AND NOT) the result was still wrong. Then I simplified the code to get to the core of it:

array_to_string(overlay_touches('field_calc_test_6eea42a9_d4df_4074_9eac_bc1b465fed54', $geometry))

Not using the array_to_string function only gave me NULL while this code gave me another error:

There is no connection between the number of commas / NULL and the number of other features each feature touches. Where am I going wrong here? This thread seemed to give me an answer but this does not work for me. I also tried asking ChatGPT but to no result.

1 Answer 1


Overlay_disjoint when used with the [,expression] parameter will return an array of all polygon that are disjoint from the current feature so unless a polygon intersect all other polygon it will always return something, remove the [,expression] parameter to get a boolean stating if the current feature intersect something or not.

The same is true for all other, you don't need to use the [,expression] parameter to test the relationship, it is only usefull to get the list of intersecting feature (and using it with $geometry won't get you that)

In the expression below I use the boolean test first then use the [,expression] parameter with the name of the polygon to output more informative result like "intersect C" rather than only "intersect" as you did. You may want to split the overlay type and the polygon(s) concerned in two different column rather than putting all in the same place as I did :

    WHEN overlay_equals('TEST_c4dd4034_d7cf_40e9_80aa_a5cc8ec77157') THEN 'equals'|| ' ' ||array_to_string(overlay_equals('TEST_c4dd4034_d7cf_40e9_80aa_a5cc8ec77157', "NAME"))
    WHEN overlay_touches('TEST_c4dd4034_d7cf_40e9_80aa_a5cc8ec77157') THEN 'touches'|| ' ' ||array_to_string(overlay_touches('TEST_c4dd4034_d7cf_40e9_80aa_a5cc8ec77157', "NAME"))
    WHEN overlay_disjoint('TEST_c4dd4034_d7cf_40e9_80aa_a5cc8ec77157') THEN 'Disjoint'
    WHEN overlay_within('TEST_c4dd4034_d7cf_40e9_80aa_a5cc8ec77157') THEN 'within'|| ' ' ||array_to_string(overlay_within('TEST_c4dd4034_d7cf_40e9_80aa_a5cc8ec77157', "NAME"))
    WHEN overlay_contains('TEST_c4dd4034_d7cf_40e9_80aa_a5cc8ec77157') THEN 'contains'|| ' ' ||array_to_string(overlay_contains('TEST_c4dd4034_d7cf_40e9_80aa_a5cc8ec77157', "NAME"))
    WHEN overlay_intersects('TEST_c4dd4034_d7cf_40e9_80aa_a5cc8ec77157') THEN 'intersect'|| ' ' ||array_to_string(overlay_intersects('TEST_c4dd4034_d7cf_40e9_80aa_a5cc8ec77157', "NAME"))
    ELSE 'no_overlap'

Note that the ELSE 'no_overlap' is never used as the overlay_disjoint take care of that case...

Also as CASE statement are read and evaluated sequentially if a polygon touch one polygon and intersect a third one the output will be "touches" but not "intersect" (as overlay_touches came before overlay_intersects in the expression it will be evaluated first and this will be the only result you get, see polygon L,M and N below) so you may want to order the different overlay_xxxxx in a way that give you the relation that you judge the most important first.

enter image description here

  • That looks like a great solution! Interesting that the disjoint function does not work. I will try your solution as soon as I can, thank you very much for a detailed answer!
    – Homerun_
    Commented Sep 13, 2023 at 9:48
  • 1
    @Homerun_ I heavily edited my answer as I realize that disjoint was in fact working as intended...
    – J.R
    Commented Sep 13, 2023 at 11:11
  • Thank you so much! I used your solution to calculate one column per overlay type and then one column that just appends all these. I can then select by expression (e.g. "Overlay" LIKE '%disjoint%') to select the polygons I am interested in. Thank you so much for your efforts! :)
    – Homerun_
    Commented Sep 14, 2023 at 13:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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