5

I have 2 existing vector layers: 1 point, 1 polygon. In almost all cases the points overlap a polygon. Both layers share a field (Zone id).

Point layer attributes

Polygon layer attributes

The point layer has multiple existing features (1,000+) within which I need to populate the 'Zone id' field, taking the value from the polygon feature with which each one overlaps. How do I do this quickly and directly, i.e. without doing it manually, creating new layers, adding additional fields, and so on?

Ideally I would like this to then happen automatically whenever new point features are created/edited that overlap polygons on the other layer. Even better would be able to do this for linear and polygon features, where they overlap the original polygon layer as well – in some cases though they would overlap multiple features, so this may be impossible.

I originally asked a version this question in 2019, and it was solved using the refFunctions plugin (see below), but that is no longer available, and so far as I can see there are no standard functions now that do quite the same thing.

Again, I have looked at various similar threads on here, but none of them quite answer the question.

I am currently using QGIS 3.28.15

asked Dec 17, 2019 at 14:24
1
  • 2
    Please specify, whether you want to do this upon feature creation, or for already existing features. - Edit: Maybe provide an excerpt of the attribute tables. Commented Dec 17, 2019 at 14:28

4 Answers 4

9

No need to use PyQGIS. You can do this with the Field Calculator.

  1. Install the RefFunctions Plugin (this gives you access to new functions like geomintersects).
  2. Use the Field Calculator to update the field in the point layer using this expression:

    geomintersects( 'polygons','ID')
    

    where 'polygons' is the name of your polygon layer, and 'ID' is the name of the field in the polygon layer that you want to copy into the point layer.

This will populate the field with the polygon values. To keep the field up-to-date, there are several options. Choose the one that best suits your needs:

  • Every time you add new features, repeat the steps above to update the field. However, there's no need to do it one at a time. Make as many new features as you want, then select the new features and update the field using the Field Calculator and choosing the options "update existing field" and "selected features only".

  • Use the Field Calculator to add a virtual field, using the same expression as above. The virtual field will be always updated whenever there's a change. When you create new features, they will always have the updated information. However, a virtual field is only stored in the project, so if you import the layer into a different project this field will be missing.

  • Use a default value for the field. Open the layer properties> attribute form. Input the same expression in the "default value" text box.

    enter image description here

answered Dec 17, 2019 at 17:38
9
  • 1
    How does geomintersects handle cases where two polygons overlap? Commented Dec 17, 2019 at 18:03
  • 2
    It picks one of them. (I have no information about how it makes the choice.) If you have specific requirements for multiple overlapping polygons, you can make it conditional on intersecting_geom_count('polygons') = 1, and handle any other cases manually. Commented Dec 17, 2019 at 18:08
  • That works perfectly. I'm finding the 'apply default value on update' option is particularly useful when editing the layers Commented Dec 18, 2019 at 14:14
  • 1
    @JimS-W Generate an 8-figure grid reference based on what? Does the grid reference already exist as an attribute in another layer? Or will it be randomly generated? Does it need to be unique? Will it be calculated somehow, and if so on what basis will it be calculated? This is probably complicated enough to be a separate question. Commented Dec 18, 2019 at 16:31
  • 1
    That's possible. Check the plugin manager and see if you need to update your version of the plugin. If that doesn't fix it, make a bug report. Commented Jan 9, 2020 at 16:31
5

For your first part, you can use the tool Join attributes by location

enter image description here

answered Dec 17, 2019 at 14:34
4
  • Looks like that should work, with a bit of experimentation, thanks. Is there any way to do this directly within the existing points layer, rather than creating a new layer? Commented Dec 17, 2019 at 15:22
  • 1
    You can by using PyQGIS but it's a little more complicated. You can validate my answer if it suits you. Commented Dec 17, 2019 at 15:26
  • Not familiar with PyQGIS - would this allow it to do it automatically for new features? Commented Dec 17, 2019 at 15:43
  • 1
    This is possible by using "signal" but it is still an additional degree of difficulty so don't start with that to discover the language. Commented Dec 17, 2019 at 15:46
1

You can use this expression in the field calculator without the need to install a plugin - substitute your layername for base and fieldname for code.

array_first(overlay_intersects('base',"code"))

The layername is case sensitive.

If you need to get the value for the polygon with the greatest area use this:

attribute(get_feature('base','fid',map_get(array_first(overlay_intersects(layer:='base', expression:= geom_to_wkt(@geometry), return_details:=true, sort_by_intersection_size:='des')),'id')), 'code')
answered Sep 27, 2024 at 13:59
1
+50

Same workflow as in @csk's answer, but use the native aggregate function in conjunction with the native intersects instead; this lives inside an aggregate function as an intersection query may return multiple matches.

You thus have two options:

  • get an array with all matches:
    aggregate(
     layer := '<layer>',
     aggregate := 'array_agg',
     expression := "<column>",
     filter := intersects( @geometry, geometry( @parent ) ),
     order_by := <ordering_expression_or_column>
    )
    
  • get the n-th element of that same array, irrespective of potential other matches:
    (
     aggregate(
     layer := '<layer>',
     aggregate := 'array_agg',
     expression := "<column>",
     filter := intersects( @geometry, geometry( @parent ) ),
     order_by := <ordering_expression_or_column>
     )
    )[<n>]
    

Update 1 (as per comments):

Substitute (but keep all wrapping ' & "):

  • <layer> with the name of the Polygon layer name
  • <column> with the name of the Polygon layer field name you want to get returned
  • <n> with the array index of the returned array of <column> values you are interested in: i.e. if there are never more than a single Polygon that contains a Point each, set to 0
  • [optionally] <ordering_expression_or_column> with an expression or column/field you want the array of intersecting Point <column> values to be ordered by; you want this for cases where a Point can be contained in multiple Polygons; remove the order_by if that is never the case

See the docs for more info.

Update 2 (as per comments):

Since you are working with String fields here, and you seem to be wanting a list separated by ; in cases where a Point is contained in multiple Polygons, use concatenate_unique as function aggregator; tailored to your setup:

aggregate(
 layer := 'Zones_basic_af9ad488_96e8_4cb9_bbd6_09d78df84024',
 aggregate := 'concatenate_unique',
 concatenator := '; ',
 expression := "Zone id",
 filter := intersects( @geometry, geometry( @parent ) ),
 order_by := to_int( "Zone id" )
)

I'd prefer the Default value option - after a re-calc of all present relations as explained - if all you ever do is adding points; if you need to have all possible updates on both layers (i.e. adding polygons, moving points or polygons, deleting polygons) projected, you will have to use a Virtual column; note that those columns are Project bound and are not stored inside the actual layer!

answered Sep 27, 2024 at 12:28
7
  • Please can you explain this further, ideally with an example of what I should be entering into the Field Calculator. The source layer is 'Zones_basic_af9ad488_96e8_4cb9_bbd6_09d78df84024', and the field is 'Zone id' Commented Sep 30, 2024 at 9:17
  • 1
    @JimS-W see my updates. I will say, the newer overlay_* functions use a more sophisticated robustness model and may or may not benefit from the GEOS internal per-feature in-memory indexation. I cannot see any performance gains for layers with feature counts <10000 and non-overlapping areas for PIP operations, though. Commented Oct 1, 2024 at 6:29
  • Thank you, I have tried that as per your update, but with no joy - the Field Calculator shows the correct result in the preview, although in brackets, e.g. ['25'], but when I press OK nothing happens - the value remains NULL Commented Oct 1, 2024 at 8:32
  • 1
    @JimS-W again, see my update. The reason you are not seeing anything being propagated to the attribute table is that the results are arrays, while the field expects a String. RE: numerical order: these are String values and even though they represent numeric values, they do not inherit their inherent ordering. You will have to cast them first - see the order_by expression in my update. Commented Oct 1, 2024 at 10:38
  • 1
    @JimS-W there certainly is some way to you get that order - e.g. you can first try to order_by a different field (fid maybe?), and if that doesn't work try string manipulation e.g. via substring or splitting into numbers and letters etc. - but that is dependent on your data. Go wild, trial and error your way to success ,) Commented Oct 1, 2024 at 15:45

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.