As far as I know, the filter expression (fourth parameter of aggregate
) can only access fields and values from the referenced layer (layerA
in your example) and not from the layer you're going to store results into (i.e., layerB
). Two possible solutions could be:
- To use the
Join attributes by location
from Processing (select contains
as Geometric predicate
and the value sum
for Statistics for summary
).
- To use the
Function Editor
and use Python code.
I'll elaborate more on the second solution.
A function with the following parameters would be handy in your case:
- Name of the layer with all roads (
layerA
for you).
- Name of the field from
layerA
to aggregate (fieldAA
for you).
- Current feature geometry (from layer with the shortest path).
So, go to Field Calculator
, open the Function Editor
, create a new file, and paste this code:
from qgis.core import ( qgsfunction, QgsSpatialIndex, QgsFeatureRequest,
QgsMapLayerRegistry )
index_cache = {}
def get_layer_index( layer ):
if layer.name() not in index_cache:
index_cache[layer.name()] = QgsSpatialIndex( layer.getFeatures() )
return index_cache[layer.name()]
@qgsfunction(args='auto', group='Custom')
def sum_contains(road_layer_name, field_name, geometry, feature, parent):
layers = QgsMapLayerRegistry.instance().mapLayersByName( road_layer_name )
if not layers:
raise Exception( "The layer could not be found." )
layer = layers[0]
intersectIds = get_layer_index( layer ).intersects( geometry.boundingBox() )
features = layer.getFeatures( QgsFeatureRequest().setFilterFids( intersectIds ) )
return sum( f[field_name] for f in features if geometry.contains(f.geometry()) )
Click on Load to register the functions above, go to the Expression
tab and write this expression:
sum_contains( 'layerA', 'fieldAA', $geometry )
Still in the Field Calculator
, create/choose your target field (from layerB
) and click OK
.
You should get the sum of fieldAA
from only those features in layerA
contained by each feature in layerB
.
NOTE 1: The sum_contains()
function uses a cache to avoid building the whole spatial index for each feature (source: How to Use Function Editor in QGIS Field Calculator).
NOTE 2: Once you use the aforementioned expression the cache is built for the road layer, so, if you edit features from such layer and you want to use the sum_contains()
function again, you would need to go to Function Editor
, select the file you created, and click on Load
so that the index is initialized again. Otherwise, you would be using an outdated index.
EDIT: Adding in-line comments to explain the code.
from qgis.core import ( qgsfunction, QgsSpatialIndex, QgsFeatureRequest,
QgsMapLayerRegistry )
index_cache = {} # Global variable to store spatial index for layers
def get_layer_index( layer ):
# Python function to check whether we already built an index for
# a given layer. If so, just return the built index, otherwise
# build it now and store it in the global variable for further calls.
if layer.name() not in index_cache:
index_cache[layer.name()] = QgsSpatialIndex( layer.getFeatures() )
return index_cache[layer.name()]
@qgsfunction(args='auto', group='Custom')
def sum_contains(road_layer_name, field_name, geometry, feature, parent):
# First, check if we can access the road layer
layers = QgsMapLayerRegistry.instance().mapLayersByName( road_layer_name )
if not layers:
raise Exception( "The layer could not be found." )
layer = layers[0]
# Second, obtain ids of road features that intersect the
# current geometry BBox. This uses a spatial index
# to optimize the spatial search. Features that intersect the BBox
# likely intersect the geometry itself, but we need to check that later.
intersectIds = get_layer_index( layer ).intersects( geometry.boundingBox() )
# From the step above we get a list of matching feature ids,
# Now we use them to request the feature objects.
features = layer.getFeatures( QgsFeatureRequest().setFilterFids( intersectIds ) )
# Now we perform a spatial search using the geometry predicate
# 'contains' with current geometry and all features that
# intersect its BBox, because they are great candidates to match.
# We do this in one line (see Python list comprehensions):
# iterate (road) features, validate the 'contains' predicate,
# and list field values of matching features. Finally, we sum
# the list of values and return the total.
return sum( f[field_name] for f in features if geometry.contains(f.geometry()) )
Best Answer
It turns out that the filter you are passing to the aggregate function always returns True for any feature.
In other words, what you have at the left hand side of the condition is the same as you have on the right hand side.
Why?
Because
"Field2"
gets the value of fieldField2
for the current feature, and that's exactly what theattribute
function will do, because you're asking for the value of the field called'Field2'
(second parameter) for the current feature (first parameter:$currentfeature
).Keep in mind that the filter expression can only access fields and values from the referenced layer (
'Layer'
in your example) and not from the layer you're going to store results into.So, you need to pass a filter that not always evaluates to
True
, or it will not make sense to pass it at all.Example of valid filters (it would depend on your field values, of course):