4

Using the built-in labeling engine in QGIS, I am able to place multiple labels within my mapframe according to defined styles and a placement hierarchy. I would like to extract these placed labels (and especially their bounding geometry) to use them in a different context.

I am aware there is a built-in processing-tool called "Extract labels". Unfortunately, this tool only extracts the label placement as a point feature. This poses challenges when working with curved labels (such as labels along rivers).

Is there any way to get the label and its "bounding polygon"? Below an image of what this could look like.

bounding box of placed text label

2
  • Should it be somehow depending on the zoom level? What is the size of the label, which units are used? Commented Sep 27, 2022 at 13:58
  • yes it should be depending on a set map scale. the size of the label (and all other labeling properties) is defined in the qgis-style (.qml). one main property of the label size would be the font size which can be in mm, points, pixels, meters at scale, etc. According to all the style-settings and the current map scale, the labeling engine places the labels on the map. i would like to extract the boxes surrounding these placed labels as polygons so i can use them somewhere else.
    – lifra
    Commented Sep 28, 2022 at 7:03

1 Answer 1

3

Not 100% what you are looking for but hopefully a start. This will only work for labels which are in Curved placement mode. You need to select the layer from which you want to calculate the polygons based on the labels. Only the labels in the current view will be taken into account. The script iterates over all labels, creates a temporary line from each center point of each label character and finally creates a buffer based on the height.

# Access selected layer
labellayer = iface.layerTreeView().selectedLayers()[0]

# Get canvas, labels in canvas and current extend
mc = iface.mapCanvas()
lr = mc.labelingResults()
extent = mc.extent()

# Prepare temporary layer
vl = QgsVectorLayer('Polygon?crs=epsg:3857', 'polygon' , 'memory')
vlProv = vl.dataProvider()

# Helper Dict where we store the center points of each label character and the height
linePoints = {}

# Iterate over all labels
for lrl in lr.labelsWithinRect(extent):
    
    # Create a temporary dict per each label and store the height + list for the points
    if lrl.featureId not in linePoints:
        linePoints[lrl.featureId] = {}
        linePoints[lrl.featureId]["height"] = float(lrl.height)
        linePoints[lrl.featureId]["points"] = []
        
    if lrl.layerID == labellayer.id():
        # Add each point to the point list
        geom = QgsGeometry.fromRect(lrl.labelRect)
        point = QgsPoint(geom.centroid().asPoint())
        linePoints[lrl.featureId]["points"].append(point)
        
# Iterate over the helper dictionary 
for key, value in linePoints.items():
    # First we create a temporary line geometry of each of the character points
    lineGeom = QgsGeometry.fromPolyline(value["points"])
    
    # Finally we create a buffer polygon from the line using the height
    polygonFeat = QgsFeature()
    bufferGeom = lineGeom.buffer(value["height"], 100)
    polygonFeat.setGeometry(bufferGeom)
    
    vlProv.addFeatures([polygonFeat])
    vl.updateExtents()

# Add the layer to the map
QgsProject.instance().addMapLayer(vl)

enter image description here

EDIT: As mentioned by @Matt a different QgsGeometry.JoinStyle can be applied. Unfortunately I couldn't find a perfect solution as sometimes half of the last character is clipped of. Another improvement is to devide the height by two so the buffer fits more accurate.

    # Finally we create a buffer polygon from the line using the height
    polygonFeat = QgsFeature()
    bufferGeom = lineGeom.buffer((value["height"] / 2), 100, QgsGeometry.EndCapStyle(3), QgsGeometry.JoinStyle(0), 0)
    polygonFeat.setGeometry(bufferGeom)
1
  • 3
    Nice approach. You could also try using the flat end cap style to square off the ends of the buffers.
    – Matt
    Commented Sep 28, 2022 at 9:45

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.