2

I am trying to get the bounding box of my labels in a vector layer in PyQGIS (Version: 3.30.2-'s-Hertogenbosch) with the following script but I need to create a vector layer from a WKT file and not a GeoPackage file.

But here is my attempt to to render the labels with the source of the vector layer being a WKT file. Notice I am using a style file as well.

I am getting the following error - see Zero Results comment below: 'NoneType' object has no attribute 'allLabels' Why am I getting zero results for the labels with the WKT file but not using a GeoPackage file. Does a GeoPackage file contain more or different information that would effect this?

layer_name = 'MyLayer'
crs = QgsCoordinateReferenceSystem()
crs.createFromWkt(wkt)

layer = QgsVectorLayer('Point?crs=epsg:4326', layer_name, 'memory')
layer.setCrs(crs)
layer.startEditing()

# Add attribute field for label text
field_name = "Name"
field = QgsField(field_name, QVariant.String)
layer.addAttribute(field)

# Open the input file
with open(input_file, "r") as file:
    header = file.readline().strip().split("|")
    label_index = header.index(label_column)

    for line in file:
        values = line.strip().split("|")
        wkt = values[0]
        label = values[label_index]
        feature = QgsFeature()

        geometry = QgsGeometry.fromWkt(wkt)
        print(QgsWkbTypes.displayString(geometry.wkbType()))
        if geometry.wkbType() == QgsWkbTypes.Point:
            print(geometry.asPoint())

        feature.setGeometry(geometry)
        feature.setAttributes([label])
        layer.addFeature(feature)


print("Feature Count:", layer.featureCount())

if layer.isValid():

    QgsProject.instance().addMapLayer(layer)

    canvas = QgsMapCanvas()
    canvas.setLayers([layer])
    canvas.setExtent(layer.extent())

    layer_settings = QgsPalLayerSettings()
    layer_settings.fieldName = field_name

    layer_settings = QgsVectorLayerSimpleLabeling(layer_settings)
    layer.setLabelsEnabled(True)
    layer.setLabeling(layer_settings)

    map_settings = canvas.mapSettings()
    render = QgsMapRendererParallelJob(map_settings)
    render.start()
    render.waitForFinished()
    labeling_results = render.takeLabelingResults() # <== Zero Results
    # labels = labeling_results.allLabels()
    
    # if labels:
    #     for l in labels:
    #         if l.featureId == feature.id() and l.layerID == layer.id():
    #             geometry = l.labelGeometry

    for feature in layer.getFeatures():

        print("Dump features ...")
        attributes = feature.attributes()
        print("Feature ID:", feature.id())
        print("Geometry wkt:", feature.geometry().asWkt())
        print("Geometry alternate:", feature.geometry())

        label_field_name = layer.labeling().settings().fieldName
        print("Attributes:", attributes)
        for idx, attribute in enumerate(attributes):
            print(label_field_name + ":", attribute)
            print("------")

GeoPackage approach (this works!):

layer = QgsVectorLayer(layer_file, "my-layer", "ogr")

canvas = QgsMapCanvas()
canvas.setLayers([layer])
canvas.setExtent(layer.extent())

layer_settings = QgsPalLayerSettings()
layer_settings.fieldName = "name"

layer_settings = QgsVectorLayerSimpleLabeling(layer_settings)
layer.setLabelsEnabled(True)
layer.setLabeling(layer_settings)

map_settings = canvas.mapSettings()
render = QgsMapRendererParallelJob(map_settings)
render.start()
render.waitForFinished()

labeling_results = render.takeLabelingResults()
labels = labeling_results.allLabels()
print(labels)

if labels:
        for l in labels:
            print("label labelRect: ", l.labelRect)
            print("label labelText: ", l.labelText)
            print("label width: ", l.width)
            print("label height: ", l.height)
            geometry = l.labelGeometry
            print(geometry)
            label_bounding_box = geometry.boundingBox()  
            print("bounding box: ", label_bounding_box)
            print("bb center: ", label_bounding_box.center)
            print("bb x1: ", label_bounding_box.xMinimum)
            print("bb y1: ", label_bounding_box.yMinimum)
            print("bb x2: ", label_bounding_box.xMaximum)
            print("bb y2: ", label_bounding_box.yMaximum)
1
  • @bugmenot123 I resubmitted my question with a code fragment for review. The code is based on digging through the QGIS API C++ source code, online documentation, Python debugger references, example from Vsevolod Tsukanov and others.
    – bdelliott
    Jul 23 at 22:10

2 Answers 2

4

I'm assuming that label bounds can only be obtained after rendering. Rendering is performed for a specific location (extent) on the canvas.

The simplest way to get the bounds of the label from the current canvas is:

canvas = iface.mapCanvas()
labels = canvas.labelingResults().allLabels()
for l in labels:
    print(l.labelRect)

Here is a more complex example of getting label geometry. The "get_label_geometry" function independently renders each selected feature on the active layer and returns the geometry of its label.

In this example, rendering is performed at a scale of 20_000, the projection of the feature layer should be EPSG:4326.

In this example, we'll get the label geometry (in the layer's coordinate system) for all selected features, including those that are "outside" the current extent of the canvas.

def get_label_geometry(layer, feature):

    """returns the label geometry"""

    # GEOMETRY PROJECTION TRANSFORMATION OBJECTS
    # coordinate system of the current QGIS project
    map_crs = iface.mapCanvas().mapSettings().destinationCrs()
    # layer coordinate system (EPSG:4326)
    lyr_crs = QgsCoordinateReferenceSystem(4326)  
    # geometry to map projection
    to_map_crs = QgsCoordinateTransform(lyr_crs, map_crs, QgsProject.instance())
    # geometry to layer projection
    to_lyr_crs = QgsCoordinateTransform(map_crs, lyr_crs, QgsProject.instance())

    canvas = iface.mapCanvas()
    map_settings = canvas.mapSettings()
    zoom_scale = 20_000
    extent = feature.geometry().boundingBox()
    extent = to_map_crs.transformBoundingBox(extent)
    extent = map_settings.computeExtentForScale(extent.center(), zoom_scale)
    map_settings.setExtent(extent)
    render = QgsMapRendererParallelJob(map_settings)
    render.start()
    render.waitForFinished()
    render.cancel()
    labeling_results = render.takeLabelingResults()
    labels = labeling_results.allLabels()
    if labels:
        for l in labels:
            if l.featureId == feature.id() and l.layerID == layer.id():
                geometry = l.labelGeometry
                geometry.transform(to_lyr_crs)
                return geometry
                    
layer = iface.activeLayer()
features = layer.getSelectedFeatures()
for f in features:
    geom = get_label_geometry(layer, f)
    print(geom)
6
  • Isn't this for a Python script run within QGIS (using iface)? Sorry not to clarify but I am running a standalone script. But I might be able to create a mapCanvas apart as a standalone?
    – bdelliott
    Jul 21 at 14:39
  • Yes, this code works in python panel in QGIS. I'm not sure if it's possible to adapt the above code to a stand alone script. I assume that in a stand alone script it is also possible to create a QgsMapCanvas object, add layers to it and set label parameters to them. Then render this canvas, and get the label geometry as a result of rendering. The labels don't exist without rendering the canvas. Jul 21 at 15:28
  • @VsevolodTsukanov Your code works fine for me. But calculated bounding boxes of labels always bigger than highlighted bbox of label (when i'm trying to move label on map). Don't you know what can be the problem?
    – BlackTesta
    Sep 22 at 11:52
  • @BlackTesta Is the scale specified in the script (zoom_scale) and the scale at which you move the labels on the map the same? Is the geometry of the label layer stored in EPSG:4326? Sep 22 at 12:19
  • @VsevolodTsukanov Yes scales are the same. Crs is local projected with meter units. When map scale is 10000 i need about 7000 inside script to get labels bounding box has right size.
    – BlackTesta
    Sep 25 at 4:31
1

Here is a standalone script that worked well for me.

from qgis.core import (
    QgsApplication,
    QgsVectorLayer,
    QgsMapRendererParallelJob,
    QgsVectorLayerSimpleLabeling,
    QgsPalLayerSettings
)
from qgis.gui import QgsMapCanvas


qgis_path = "C:/Program Files/QGIS 3.30.1/"
QgsApplication.setPrefixPath(qgis_path, True)
qgs = QgsApplication([], False)
qgs.initQgis()

layer = QgsVectorLayer("D:/mus/test_points.gpkg", "НАЗВАНИЕ_СЛОЯ", "ogr")

canvas = QgsMapCanvas()
canvas.setLayers([layer])
canvas.setExtent(layer.extent())

layer_settings = QgsPalLayerSettings()
layer_settings.fieldName = "name"

layer_settings = QgsVectorLayerSimpleLabeling(layer_settings)
layer.setLabelsEnabled(True)
layer.setLabeling(layer_settings)

map_settings = canvas.mapSettings()
render = QgsMapRendererParallelJob(map_settings)
render.start()
render.waitForFinished()

labeling_results = render.takeLabelingResults()
labels = labeling_results.allLabels()
print(labels)

I find it more convenient to test this code in QGIS, as there you can use canvas.show() to see the style of the layer and its labels. And then transfer this code to a standalone script.

10
  • Thanks. But I am getting the following error for the line: labeling_results = render.takeLabelingResults() - Here is the error: 'NoneType' object has no attribute 'allLabels'
    – bdelliott
    Jul 25 at 15:01
  • @bdelliott I turned the answer into a standalone script, it works for me. Jul 25 at 16:42
  • Thank you so much for the code and taking the time! Unfortunately, I still get the same error: 'NoneType' object has no attribute 'allLabels' Could it be my version of PyQGIS is different than yours: PyQGIS Version: 3.30.2-'s-Hertogenbosch ?
    – bdelliott
    Jul 26 at 14:12
  • @bdelliott Too bad it didn't help. Have you changed in the code of my answer only the path to the file and the name of the label field? And QGIS path... Jul 26 at 14:44
  • I have updated my code. Would you mind sharing what version of PyQGIS you are using: pyqgis_version = Qgis.QGIS_VERSION (code above)
    – bdelliott
    Jul 27 at 14:09

Your Answer

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

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