35

Is there a way to select an attribute from a polygon layer and insert the value into a Virtual field of a point layer using "within" in the Field Calculator?

CASE
    WHEN within($geometry, geometry_polygon) THEN attribute_polygon
END

enter image description here

5
  • 1
    Why not use the 'Point sampling tool' plugin for this?
    – Jakob
    Jun 25, 2015 at 21:14
  • Because I need dynamic updates when creating new points or moving existing points.
    – Lunar Sea
    Jun 26, 2015 at 8:21
  • You would be better off scripting this interaction rather than relying on out of the box tools.
    – nagytech
    Jun 28, 2015 at 5:00
  • Unfrotunately I don't have any scripting experience.
    – Lunar Sea
    Jun 28, 2015 at 7:03
  • 1
    @LunarSea I've written an example below for you to follow, but you may have to tweak it to fit your needs.
    – nagytech
    Jun 29, 2015 at 1:29

3 Answers 3

29

For QGIS versions QGIS 3.16 and higher

Since QGIS 3.16 the functionality of the "refFunctions plugin" was emended into core. The current corresponding function is the overlay_within().

For QGIS versions lower than QGIS 3.16

Spatial joins are available in the Field Calculator after installing the "refFunctions" plugin.

geomwithin(targetLayer, targetField)

Keep in mind that:

This plugin is deprecated!

2
  • Plug-ins are much simpler than using a custom script. Thanks!
    – jpmc26
    Jan 30, 2017 at 22:55
  • 1
    geomwithin('targetLayer','targetField').
    – Raja
    Mar 13, 2017 at 8:28
23
+50

Out of the box, field calculator does not support spatial joins across feature layers. But, if you have a look at NathanW's post on the function editor for qgis expressions you will be able to make out that we can script our own data interaction.

The following script will allow you to express what you're after. It works by iterating through all features on the polygon layer and if there is a spatial join, then reference tabular data from the specified column:

from qgis.core import *
from qgis.gui import *
from qgis.utils import iface

allfeatures = None
index = QgsSpatialIndex()
indexMade = 0
refLayer = None

@qgsfunction(args="auto", group='Custom')
def spatialJoinLookup(layerName, refColumn, defaultValue, geom, feature, parent):

    if geom is None:
        return defaultValue

    # globals so we don't create the index, refLayer more than once
    global allfeatures
    global index
    global indexMade
    global refLayer

    # Get the reference layer
    if refLayer is None:
        for layer in iface.mapCanvas().layers():
            if layerName == layer.name():
                refLayer = layer
                break
    if refLayer is None:
        raise Exception("Layer [" + layerName + "] not found")

    # Create the index if not exists
    if indexMade == 0:
        index = QgsSpatialIndex()
        allAttrs = layer.pendingAllAttributesList()
        layer.select(allAttrs)
        allfeatures = {feature.id(): feature for (feature) in refLayer.getFeatures()}
        for f in allfeatures.values():
            index.insertFeature(f)
        indexMade = 1

    # Use spatail index to find intersect 
    fid = None
    ids = index.intersects(geom.boundingBox())
    for id in ids:
        fid = id
        break # Only get the first match.
    if fid is not None:
        return allfeatures[fid].attribute(refColumn)

    # Default
    return defaultValue

Polygon Layer Example

Below is an example of a polygon layer that you might have. I've also created a corresponding point layer that you will see in the final image.

enter image description here

Expression Usage

Note, if you want to use a separate column you must change the second argument to match the column name in the polygon dataset. Example, you could use the 'AreaNumber' column, but would have to match the column type in the field calculator settings.

enter image description here

Result

You can see that the default column value has been applied where there is no spatial join, and the other's have matched the correct data. Note the script I've given will only join on the first match. You would need to create some other business logic if your polygons were overlapping.

enter image description here

8
  • Many thanks, your script is working fine using 'geom' instead of 'geometry' in the first 'if' statement. Joining geometries this way may be quite useful e.g. for creating multiple labels on polygons.
    – Lunar Sea
    Jun 29, 2015 at 10:01
  • Sorry, I don't know how I missed that. Hopefully there's no performance issues - I only tried it with a very small subset of records.
    – nagytech
    Jun 29, 2015 at 10:08
  • Having only 100+ point features QGIS struggles with performance issues. Adding a new point feature is really a pain even there is no point feature displayed in the acutal canvas. Otherwise when zooming in QGIS speeds up. I've tried `CASE WHEN $scale < 10000 THEN spatialJoinLookupI('Polygons', 'AreaName', 'None', $geometry) END' but it's not working. Is there anything I can do to improve the performance?
    – Lunar Sea
    Jul 4, 2015 at 19:15
  • @LunarSea I've updated the function to use a spatial index. It should be reasonably faster.
    – nagytech
    Jul 6, 2015 at 10:00
  • Thanks for your help. The spatial join is much faster now but unfortunaetely something isn't working properly. I'm getting differnt results for points within one and the same polygon.
    – Lunar Sea
    Jul 6, 2015 at 11:10
19

It can be done in Field Calculator with function aggregate(). In point layer create new field with field calculator expression like this:

aggregate(
layer:= 'polygon_layer_name',
aggregate:='concatenate',
expression:=joining_field_name,
concatenator:=', ',
filter:=intersects($geometry, geometry(@parent))
)

Where layer is polygon layer name written like string, aggreagate is aggregate function (can be used also sum etc.), expression is field from values will be taken,concatenator is joining character string (have to be set, even in this case) and filter is filtering features based on expression (in this case interesects layer geometry with geometry of parent layer).

For more info check Aggregates QGIS documentation.

For automatic updates can be used virtual fields or you can set the expression as Default value in Attributes Form settings in Layer Properties (Attribute form setting documentation).

enter image description here

2
  • 4
    It should be noted that spatial functions (with geometry(@parent)) are supported only from QGIS 3 onwards. Just in case anyone reading this is still using 2.18...
    – she_weeds
    Mar 24, 2019 at 8:02
  • Thank you. Works like a charm. Apr 10, 2019 at 10:01

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.