6

I quite often use virtual fields. Some of them are relatively complex aggregates. Since virtual fields updates on every input, by moving the map, open the attribute table and so on, it can cost a lot of CPU-Resources.

Is there a way to suppress the permanent update of a virtual field or just update it on demand? F.e. it would be fine, if virtual fields updates just by saving the layer or ending the edit mode.

5
  • 1
    If your data is stored in postgis, you could use a materialized view (postgresql.org/docs/12/rules-materializedviews.html) and only refresh it when needed. Oct 5, 2020 at 14:42
  • I use GeoPackage and can not migrate to PostGIS
    – MartinMap
    Oct 5, 2020 at 14:59
  • 2
    Script it and link it with a layer action. Oct 5, 2020 at 15:06
  • 1
    A regular (non materialized) view would perhaps work and would be easy to test. Create the view, add it to QGIS, edit the data, pan/zoom the map and see what happens. I think you need to right-click "Update SQL Layer" on the layer to get the view to refresh within a QGIS project - see gis.stackexchange.com/questions/302302/… Oct 5, 2020 at 18:55
  • 1
    Is this a hypothetical question about "cpu resources" or is this about a real world problem? If it's the former one, a good answer will include details about when virtual fields are calculated. If it's the later one, an update to the question for the problem at hand would help. Oct 11, 2020 at 16:22

3 Answers 3

4
+50

Make the field normal (non-derivative). Runing this piece of code in QGIS as a Python script defines a recomputation function which activates before saving changes to the layer. You can implement your own logic there with any expressions for any fields.

You can also connect this function to an action button if you want to recompute on demand.

def recomputeField(vlayer):
    #recomputes field for chosen layer
    #example case: there are fields a, b and c is the derivative field

    #put your virtual field expression here
    virtualFieldExpr = 'a + b'
    expression = QgsExpression(virtualFieldExpr)

    context = QgsExpressionContext()
    context.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(vlayer))

    for f in vlayer.getFeatures():
        context.setFeature(f)
        f['c'] = expression.evaluate(context)
        vlayer.updateFeature(f)


#connect the function to vlayer.beforeCommitChanges signal
layerName = 'YOUR_LAYER_NAME'
vlayer = QgsProject.instance().mapLayersByName(layerName)[0]
vlayer.beforeCommitChanges.connect(lambda: recomputeField(vlayer))

EDIT: Some advice on how to use this code. Paste it and run it in the editor (not directly in console cause it often causes syntax errors).

a, b, and c are your field names so replace them in code with the appropriate names for your fields. Change the layerName to your desired layer name. enter image description here

8
  • Sounds like the solution I was looking for, but I don't get it working (unfortunately I have no python experience): I tried to run it with python console: no error, no result and as Action: "unable to run command". I only changed the expression for testing from 'a + b' to '1 +2'. Using QGIS 3.14, Layer: GPKG. Could you please give more details for using this code?
    – MartinMap
    Oct 11, 2020 at 7:37
  • I added some instructions in the main post. Oct 11, 2020 at 11:16
  • Try running it as a script, not in console and remember to replace a, b and c in code with proper field names Oct 11, 2020 at 11:18
  • 2
    I edited the code in accordance with Matthias's comment. The layer is chosen beforehand, instead of using iface.activeLayer() Oct 12, 2020 at 6:48
  • 1
    Thanks. Please disregard the stability comment, this only applies if it's an expression function and not the implementation here. All good. Oct 12, 2020 at 8:56
4

Virtual fields are great, but sometimes I think we get carried away with their usage within QGIS. (don't worry, im guilty too).

From a high level DB engineering perspective, ideally, calculated fields should occur either on database transactions when editing, or produce view tables, thus enabling the user to consume the view table instead. View tables would be suitable candidate replacements for virtual fields.

postGIS would be ideal, but you have mentioned your use of Geopackages. Geopackages are in essence a Sqlite database. Looks like some other users have attempted similar view table structures - see here Create view table with QGIS and Geopackage
(BTW, that link doesn't actually have a solution, it just seems to show that others have tried and that there are some limitations).

Implementing code in QGIS to trigger events may be ok, but this will tie the virtual field creation to the QGIS application itself. This may or may not meet your publishing needs - for example, if its just yourself or other QGIS users, then that is probably ok - but if you are wanting this dynamic capability in - say a web mapping application, then the QGIS solution is not abstract and dynamic.

3

QGIS supports this out of the box with "default values" and "apply on update"

See https://gis.stackexchange.com/a/211154/9839

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.