4

In answering a labeling question: Changing to alternate label if first label does not fit in QGIS? I found myself wondering if there was a way to programmatically get the extent of a composer map so it could be displayed in labels and used in expressions.

So the question arose: Is there a way to find the extent of a specific map on a specific composer programmatically in a way that could be used in the expression engine? I've come up with a solution (I'm posting as an answer below) But I'd be happy if there was a more efficient solution. If you have a better one let me know.

2 Answers 2

6

In order to use the result in an expression, a custom python expression is necessary. Here's the code I've come up with:

from qgis.core import *
from qgis.gui import *
from qgis.utils import iface

@qgsfunction(args='auto', group='Custom')
def getScale(composerName, mapName, feature, parent):
    # dictionary to store QgsComposerView.composition() items by name
    compDict = {}
    for comp in iface.activeComposers():
        # workaround to get name: read it from window title
        compDict[comp.composerWindow().windowTitle()] = comp.composition()
    mapScale = 0 # default to be returned if scale not found
    if composerName in compDict:
        mapItem = compDict[composerName].getComposerItemById(mapName)
        if mapItem:
            mapScale = mapItem.scale()
    return mapScale

The first challenge is getting a composer by name. Create a dictionary to hold the composers, then iterate over the activeComposers() list, storing them by name in the dictionary.

Next, if the composer name we're looking for is in the list, get the requested map and it's extent. Return either it or 0 to denote not found.

This works great with an atlas controlled map:

Scale shown for atlas item

In this case the expression used for the label is:

'scale' || getScale('Composer 1', 'Map 0')
0

While getting more and more familiar with the ingredible QGIS Expression Functions, I found a solution for fetching layout map extents in QGIS 3.x, even if maps are rotated.

My intention was a little bit different, because I wanted to show my Atlas page bounding boxes in an overview map, but the code could be adapted to retrieve the map corner coordinates as well.

To get the Wkt bounding boxes, I added a label item to my layout (see Expression code below) and opened all Atlas pages one after the other. Before that, I opened the QGIS Python Console to collect the output of my custom "printExpr" function afterwards (right Mouse click -> Copy). With a Paste into a new Polygon layer the task was finished.

[% printExpr(
        geom_to_wkt(
            rotate(
                map_get(item_variables('Map 1'),'map_extent'), -- unrotated map bounding box
                - map_get(item_variables('Map 1'),'map_rotation') -- negative rotation angle
            ), 
            0 -- coordinate precision
        )
) %]

Custom helper function to print the Expression Function output to the Python Console:

@qgsfunction(args='auto', group='Custom', usesGeometry=False, referencedColumns=[])
def printExpr(txt,feature,parent):
    print(txt)
    return txt