Changing to alternate label if first label does not fit in QGIS?

Here's a somewhat approximate (but hopefully effective) way to do it.

First some math. We need to figure out how many characters at a certain font-size a feature can contain. Here things / assumptions to know:

  • assuming metric units, (added slight change below which may make this work for us foot crs.)
  • font size is a measure of font height. Most font's are about half as wide as they are high.
  • using pt for font height, 1pt is roughtly 0.035CM
  • assuming your parcels are roughly rectangular / regular

To find how long an item is on screen / print we can use it's longest dimension (length or hight, or just length if your labels are horizontal only) / scale * 100 (to go from M to CM)

Then we can see if our label would fit.

You could do it all in the expression engine, but a custom python expression would be more efficient. Here is a tutorial on how to create one.

My custom expression:

from qgis.core import *
from qgis.gui import *

@qgsfunction(args='auto', group='Custom')
def labelFits(labelStr, fontPt, scaleM, feature, parent):
    # returns true if a label will fit in the feature at a given
    # font size and scale

    bbox = feature.geometry().boundingBox()

    # for CRS in us foot
    # fontWidth = fontPt * 0.0875

    # for CRS in metric
    # 1pt = 0.035CM
    fontWidth = fontPt * 0.035 * 0.5

    # length of maximum dimension on-screen/print in CM
    labelRoom =  max(bbox.width(),bbox.height()) / scaleM * 100

    # approx length of our label in CM based on font height * 0.5
    labelLen = len(labelStr) * fontWidth

    if labelRoom > labelLen:
        return True
    else:
        return False

Then just use that expression in the label:

if(labelFits( "yourAttribute" , 8 , @map_scale ), "yourAttribute",$id)

You'll need to plug in whatever attribute you're actually using of course, and change the font size from 8 to whatever you're using. Also, since fonts vary quite a bit you may need to tweak it a little bit till it looks right.

You could also use that expression to do things like change the color based on if the label fits or not.


To use this expression outside the map canvas (i.e. in a composer label or attribute table) we need a way to programmatically get the scale of a given map item. I've posted code to accomplish that in another answer here The method there gives us an expression getScale('composername','mapname') so to incorporate it into our expression:

if(
    labelFits( "yourAttribute" , 8 , getScale('composername','mapname')),
    "yourAttribute",$id)

Duplicate the layer, one with no symbology, and set different zoom levels/obstacle settings for the labels for each.