I want to find an efficient (especially in terms of memoryusage and processing speed) method to compare intersecting features by attribute/expression.
For this script I am iterating over a source_layer_vl and for each source-feature I am iterating over the intersecting overlay-features from an overlay_layer_vl which I take from a QgsSpatialIndexKDBush(). To compare the expression results I then need to get the feature or expression result somehow. This is where I am unsure of the advantages and disadvantages of the methods I have tried:
- Bulk loading overlay-features into a dictionary before iterating over the source layer, and getting the intersecting features from this dictionary instead by feature request
 - Bulk loading overlay-expression-results into a dictionary before iterating over the source layer, and getting the expression-results of the intersecting features from this dictionary instead by feature request
 - Loading the matching overlay-features via getFeature(id) one-by-one as they intersect and evaluate the expression
 
Lets say about 50% of the points do actually intersect with the polygons.
The first method seems to be by far the fastest, almost 50% faster than method 3 and 25% faster than method 2. But: Isnt this very memory expensive, especially on larger layers? And why is bulk-loading so much faster than getting features one-by-one?
I am aware this question sounds opinion-based, but I am pretty sure it can be answered with pure facts.
Here is a reproducible code, containing all three methods, just change layer and fieldnames:
from datetime import datetime
import operator
# Vars
source_layer_vl = QgsProject.instance().mapLayersByName('earthquakepolygons')[0] # a polygonlayer
overlay_layer_vl = QgsProject.instance().mapLayersByName('earthquake2')[0] # a 2D-Single-Point-Layer
source_compare_expression = QgsExpression('"fid"') # just some expression or field of the polygonlayer
overlay_compare_expression = QgsExpression('"gap"') # just some expression or field of the pointlayer
op = operator.lt # the comparisonoperator (operator.lt means where source < overlay)
# All methods
overlay_layer_idx = QgsSpatialIndexKDBush(overlay_layer_vl.getFeatures())
if overlay_layer_idx.size() == 0:
    print('Overlay layer is empty or not of type 2D-Single-Point!')
### Method 1 ###########
method_01_starttime = datetime.now()
method_01_result = {}
overlay_layer_dict = {feat.id():feat for feat in overlay_layer_vl.getFeatures()}
for source_feat in source_layer_vl.getFeatures():
    method_01_result[source_feat.id()] = 0
    
    source_feat_geom = source_feat.geometry()
    source_feat_geometryengine = QgsGeometry.createGeometryEngine(source_feat_geom.constGet())
    source_feat_geometryengine.prepareGeometry()
    
    overlay_features = overlay_layer_idx.intersects(source_feat_geom.boundingBox())
    
    source_compare_expression_context = QgsExpressionContext()
    source_compare_expression_context.setFeature(source_feat)
    source_compare_expression_context.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(source_layer_vl))
    source_compare_expression_result = source_compare_expression.evaluate(source_compare_expression_context)
    
    for overlay_feat in overlay_features:
        overlay_feat_geom = QgsGeometry.fromPointXY(overlay_feat.point()).constGet()
        if source_feat_geometryengine.intersects(overlay_feat_geom):
            overlay_real_feat = overlay_layer_dict[overlay_feat.id]
            overlay_compare_expression_context = QgsExpressionContext()
            overlay_compare_expression_context.setFeature(overlay_real_feat)
            overlay_compare_expression_context.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(overlay_layer_vl))
            overlay_compare_expression_result = overlay_compare_expression.evaluate(overlay_compare_expression_context)
            if op(source_compare_expression_result, overlay_compare_expression_result):
                method_01_result[source_feat.id()] += 1
            
method_01_endtime = datetime.now()
method_01_runtime = method_01_endtime - method_01_starttime
### Method 2 ###########
method_02_starttime = datetime.now()
method_02_result = {}
overlay_layer_dict = {}
for overlay_feat in overlay_layer_vl.getFeatures():
    overlay_compare_expression_context = QgsExpressionContext()
    overlay_compare_expression_context.setFeature(overlay_feat)
    overlay_compare_expression_context.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(overlay_layer_vl))
    overlay_compare_expression_result = overlay_compare_expression.evaluate(overlay_compare_expression_context)
    overlay_layer_dict[overlay_feat.id()] = overlay_compare_expression_result
for source_feat in source_layer_vl.getFeatures():
    method_02_result[source_feat.id()] = 0
    
    source_feat_geom = source_feat.geometry()
    source_feat_geometryengine = QgsGeometry.createGeometryEngine(source_feat_geom.constGet())
    source_feat_geometryengine.prepareGeometry()
    
    overlay_features = overlay_layer_idx.intersects(source_feat_geom.boundingBox())
    
    source_compare_expression_context = QgsExpressionContext()
    source_compare_expression_context.setFeature(source_feat)
    source_compare_expression_context.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(source_layer_vl))
    source_compare_expression_result = source_compare_expression.evaluate(source_compare_expression_context)
    
    for overlay_feat in overlay_features:
        overlay_feat_geom = QgsGeometry.fromPointXY(overlay_feat.point()).constGet()
        if source_feat_geometryengine.intersects(overlay_feat_geom):
            overlay_compare_expression_result = overlay_layer_dict[overlay_feat.id]
            if op(source_compare_expression_result, overlay_compare_expression_result):
                method_02_result[source_feat.id()] += 1
            
method_02_endtime = datetime.now()
method_02_runtime = method_02_endtime - method_02_starttime
### Method 3 ###########
method_03_starttime = datetime.now()
method_03_result = {}
for source_feat in source_layer_vl.getFeatures():
    method_03_result[source_feat.id()] = 0
    
    source_feat_geom = source_feat.geometry()
    source_feat_geometryengine = QgsGeometry.createGeometryEngine(source_feat_geom.constGet())
    source_feat_geometryengine.prepareGeometry()
    
    overlay_features = overlay_layer_idx.intersects(source_feat_geom.boundingBox())
    
    source_compare_expression_context = QgsExpressionContext()
    source_compare_expression_context.setFeature(source_feat)
    source_compare_expression_context.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(source_layer_vl))
    source_compare_expression_result = source_compare_expression.evaluate(source_compare_expression_context)
    
    for overlay_feat in overlay_features:
        overlay_feat_geom = QgsGeometry.fromPointXY(overlay_feat.point()).constGet()
        if source_feat_geometryengine.intersects(overlay_feat_geom):
            overlay_real_feat = overlay_layer_vl.getFeature(overlay_feat.id)
            overlay_compare_expression_context = QgsExpressionContext()
            overlay_compare_expression_context.setFeature(overlay_real_feat)
            overlay_compare_expression_context.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(overlay_layer_vl))
            overlay_compare_expression_result = overlay_compare_expression.evaluate(overlay_compare_expression_context)
            if op(source_compare_expression_result, overlay_compare_expression_result):
                method_03_result[source_feat.id()] += 1
            
method_03_endtime = datetime.now()
method_03_runtime = method_03_endtime - method_03_starttime
### Results ###########
print('Method 01 Results:\n' + str(method_01_result))
print('Method 02 Results:\n' + str(method_02_result))
print('Method 03 Results:\n' + str(method_03_result))
print('\n')
print('Runtime Method 01 in Microseconds: ' + str(method_01_runtime.microseconds))
print('Runtime Method 02 in Microseconds: ' + str(method_02_runtime.microseconds))
print('Runtime Method 03 in Microseconds: ' + str(method_03_runtime.microseconds))