3

I'm looking to do some advanced label blocking for point features, based on the bounds of the symbol, rather than the point itself.

It looks like I should be able to derive this from the QgsMarkerSymbol.bounds() method, but I can't figure out how to connect the dots from the layer (or feature) symbol to a QgsMarkerSymbol.

I think I'm closing in on it with this code:

layer = iface.activeLayer()
renderer = layer.renderer()
symbol = renderer.symbol()
symbolLayer = symbol.symbolLayers()[0]
props = symbolLayers.properties()

There must be something in between here that I'm just missing, right? When I

print(symbol)

It returns a qgis._core.QgsMarkerSymbol object, but

print(symbol.bounds())

returns

TypeError: QgsMarkerSymbol.bounds(): not enough arguments
5

As @Kadir Şahbaz pointed out, the QgsMarkerSymbol.bounds() method takes 3 arguments: a QPointF object, a QgsRenderContext object and a QgsFeature object.

Additionally, the documentation for the bounds() method states:

It is recommended to use this method only between startRender() and stopRender() calls, or data defined rotation and offset will not be correctly calculated.

A few other points:

I think you should construct a QgsRenderContext instance from the canvas map settings e.g.

map_settings = iface.mapCanvas().mapSettings()
render_context = QgsRenderContext().fromMapSettings(map_settings)

When using data defined override for the symbol size, I also found it necessary to create a QgsExpressionContextScope(), set the layer fields to the scope, then append the scope to the expression context associated with the render context.

The final code snippet looks like this:

layer = iface.activeLayer()
renderer = layer.renderer().clone()
map_settings = iface.mapCanvas().mapSettings()
render_context = QgsRenderContext().fromMapSettings(map_settings)
expression_context = render_context.expressionContext()
context_scope = QgsExpressionContextScope()
context_scope.setFields(layer.fields())
expression_context.appendScope(context_scope)
renderer.startRender(render_context, layer.fields())
for feat in layer.getFeatures():
    expression_context.setFeature(feat)
    sym = renderer.symbolForFeature(feat, render_context)
    point_f = feat.geometry().asQPointF()
    sym_bounds = sym.bounds(point_f, render_context, feat)
    print(sym_bounds)
renderer.stopRender(render_context)

The output is a QRectF object, and using a data defined override for symbol size, height and width values were different for each feature symbol evaluated.

4

I don't know how relevant, but you can use something like this:

layer = iface.activeLayer()
renderer = layer.renderer()
symbol = renderer.symbol()
symbolLayer = symbol.symbolLayers()[0]
props = symbolLayer.properties()

feature = layer.getFeature(0)
geom = feature.geometry()

pointF = geom.asQPointF()
context = QgsRenderContext()
bounds = symbol.bounds(pointF, context, feature)

print(bounds)

# OUT: PyQt5.QtCore.QRectF(....)

I cannot figure out how to use context. When I used "Data defined override" for the size, I expected it would give different width and height based on the icon size of the related feature, but all heights and widths were the same for all features. No problem when not using "Data defined override".

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.