26

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

  • 1
    Why not use the 'Point sampling tool' plugin for this? – Jakob Jun 25 '15 at 21:14
  • Because I need dynamic updates when creating new points or moving existing points. – Lunar Sea Jun 26 '15 at 8:21
  • You would be better off scripting this interaction rather than relying on out of the box tools. – nagytech Jun 28 '15 at 5:00
  • Unfrotunately I don't have any scripting experience. – Lunar Sea Jun 28 '15 at 7:03
  • @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 '15 at 1:29
22

Spatial joins are available in the field calculator after installing the refFunctions plugin.

geomwithin(targetLayer,targetField)
  • Plug-ins are much simpler than using a custom script. Thanks! – jpmc26 Jan 30 '17 at 22:55
  • geomwithin('targetLayer','targetField'). – Raja Mar 13 '17 at 8:28
19
+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

  • 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 '15 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 '15 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 '15 at 19:15
  • @LunarSea I've updated the function to use a spatial index. It should be reasonably faster. – nagytech Jul 6 '15 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 '15 at 11:10
8

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
    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 at 8:02
  • Thank you. Works like a charm. – spatialthoughts Apr 10 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.