In QGIS print composer I want to label a UTM grid with some specific attributes:

  • A full UTM grid reference for the labels nearest the corners of the map, rotated perpendicular to the map axis.
  • Abbreviated UTM grid references for other labels.

See the example below from a topographic map that uses this styling:

enter image description here

The abbreviated labels and superscript formatting seen can be handled with a bit of Python, for example in this question:

How to achieve Super or Subscript graticule labels in QGIS Composer windows

For the full or partial label part of this I feel it might be possible to do in a custom Python function if the bounds of the map were accessible in the label formatting context.

Is it possible to get the corresponding map's geometry when formatting a label?

For the rotation part of the question, I'd normally expect the label rotation value to have a data-driven option, but I don't see this in the print composer (maybe a feature request). A partial workaround would be to add a second grid with the appropriate rotation value but there is still a dependency on determining whether the label should be rotated or not.

I want to conditionally format the 8 labels nearest the rectangular map bounding box. The linked answer conditionally formats each 10km interval with a full UTM reference, and these intervals do not necessarily correlate well with the bounds of the map.

  • Maybe this video helps you: youtube.com/watch?v=kM-aAgFdPbI – nagib Apr 21 '17 at 20:08
  • It's a good video, but doesn't answer the question.This video doesn't explain how to conditionally format the labels nearest the corners of the map. He's conditionally labelling every Xth grid interval, which is straightforward as it's just a modulo function, but only solves the problem if that label happens to be at the corner of the map, which isn't true in this case. – Rob Apr 22 '17 at 0:10

This answer relies on a feature in QGIS 3 HEAD builds that allows composer variables to be used in an expression to retrieve the bounds of the map, a QGIS expression to determine whether a label is the one closest on that axis to it's minima or maxima and some Python to conditionally format the label.

The full labels use subscript (UTF glyph substitution) to highlight the most significant figures in the grid interval, and the abbreviated labels show only the significant figures.

I've not shown rotation of the minor labels, for clarity. This can be accomplished by moving the full and minor labels to separate grids and setting rotation formatting accordingly.

I don't doubt there are more elegant programmatic ways to achieve the same effect.


  1. Retrieve the extents of the composer map being labelled
  2. Using the labelling interval and the extents of the map, determine whether a label is the one closest to a given axis.
  3. Conditionally format matching labels in Python to super/subscript to highlight the minor grid interval.
  4. Conditionally format non-matching labels in Python to return only the minor grid interval.

Label Expression

  WHEN  @grid_axis = 'x' THEN
      WHEN x_min(map_get( item_variables('<map_id>'),'map_extent')) + 1000 >  @grid_number THEN  UTMFullLabel( @grid_number, @grid_axis)
      WHEN x_max(map_get( item_variables('<map_id>'),'map_extent')) - 1000 < @grid_number THEN  UTMFullLabel( @grid_number, @grid_axis)
      ELSE UTMMinorLabel(@grid_number)
  WHEN @grid_axis = 'y' THEN
      WHEN y_min(map_get( item_variables('<map_id>'),'map_extent')) + 1000 >  @grid_number THEN UTMFullLabel( @grid_number, @grid_axis)
      WHEN y_max(map_get( item_variables('<map_id>'),'map_extent')) - 1000 < @grid_number THEN UTMFullLabel( @grid_number, @grid_axis)
      ELSE UTMMinorLabel(@grid_number)

Python Code

from qgis.utils import qgsfunction
from qgis.gui import *

@qgsfunction(args="auto", group='Custom')
def UTMFullLabel(grid_ref, axis, feature, parent):
  rstr = gstring[-3:]   #3 last characters
  mstr = gstring[-5:-3] #the 5th-4th characters
  #either the 1st or 1-2 for the most sig figs depending if there 6 or 7 digits
  lstr = ''
  if (len(gstring) == 6):
    lstr = gstring[0] #first 2 digits
  elif (len(gstring) == 7):
    lstr = gstring[0:1]
    return str(len(gstring))
  return "{0}{1}{2}m{3}".format(sub_scr_num(lstr),mstr,super_scr_num(rstr),'E' if axis == 'x' else 'N')

@qgsfunction(args="auto", group='Custom')
def UTMMinorLabel(grid_ref, feature, parent):
  return "{:0.0f}".format(grid_ref)[-5:-3]

def sub_scr_num(inputText):
  """ Converts any digits in the input text into their Unicode subscript equivalent.
  Expects a single string argument, returns a string"""
  subScr = (u'\u2080',u'\u2081',u'\u2082',u'\u2083',u'\u2084',u'\u2085',u'\u2086',u'\u2087',u'\u2088',u'\u2089')
  outputText = ''
  for char in inputText:
    charPos = ord(char) - 48
    if charPos <0 or charPos > 9:
      outputText += char
      outputText += subScr[charPos]
  return outputText

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.