I have a point layer in which some symbols are displayed offset from the feature's true location by a given number of millimetres. As the map's zoom changes the offset symbol's geographic location changes to maintain the millimetre display distance.

I'd like to implement this approach to connect symbols to their features using lines, but to do this I need to know the geographic location of the offset symbol. To do this I need to know the geographic distance of the millimetre offset at the current zoom level and add that to the feature's coordinate.

QGIS obviously knows the scale and crs so in theory it has all the information required, however I'm not sure if a suitable function exists. If one does exist, what is it, and if one does not exist, how would I create one?

Edit to clarify: any solution that assigns fixed geographic locations to labels is inadequate. Offsetting a label using a display distance (mm) ensures the offset remains the same across different scales - this is not true of geographical offsets, which can disappear off-screen or cluster together when zooming in or out. I need an approach that calculates a geographic location based on a mm offset at any scale.

  • Besides from your question being clearer if you provided some screenshots etc., did you try to change offset unit from mm to map units? – Jochen Schwarze Mar 14 '19 at 6:51
  • The point of using mm instead of map units is that the visible distance remains the same at different scales. If using map units you only get the desired result at a single scale – tomfumb Mar 14 '19 at 14:05

The simplest approach is to use the Easy Custom Labelling plugin that will give you results as per the link you reference and without any need for coding.

Here's a YouTube video for a rather old version of the plugin, but it will give you an idea (we are now on v 2.1)

| improve this answer | |
  • AFAIK this only sets the geographic location of the label to a fixed position, and I could do this using data driven labeling rules with no need for the plugin. The point of my question is to use a display unit offset (mm) so that as the scale changes the label does not move relative to the feature. In this video the labels would disappear off screen if you zoomed in – tomfumb Mar 14 '19 at 14:13

I coded a solution in QGIS's function editor that more or less solves the problem. There is a wrinkle in that the scale can be incorrect in a layout, but the problem as originally stated is solved so I'll post the solution here and start a new question to address the layout issue.

Here is the geometry generator line in action at different scales (it connects the soup bowl to the junction):

Screenshot of the solution in action

Screenshot of the solution in action

Expression used to generate the line

make_line($geometry, offset_point_by_display_distance("DISP_OFF", 1000))


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

@qgsfunction(args='auto', group='Custom')
def offset_point_by_display_distance(displayOffsetStr, conversionFactor, feature, parent):
    scale = iface.mapCanvas().scale()
    locationPoint = (QgsPoint)(feature.geometry().asPoint())
    if displayOffsetStr != NULL:
        offsetParts = displayOffsetStr.split(",")
        offsetX = float(offsetParts[0])
        offsetY = float(offsetParts[1])
        labelPoint = locationPoint.project(offsetX / conversionFactor * scale, 90)
        labelPoint = labelPoint.project(offsetY / conversionFactor * scale, 180)
        return (QgsGeometry)(labelPoint)
        return (QgsGeometry)(locationPoint)

displayOffsetStr is the feature attribute providing the display distance offset, and has the format "x,y". I made no effort to validate the string format at this point

conversionFactor is the number of display units (mm) in each coordinate system unit (m)

To anyone interested here is the wrinkle: iface.mapCanvas().scale() gives the scale of the map in the main QGIS window, not the scale in any of the layout maps, so if the map's scale doesn't match the layout's scale the line lengths will be calculated incorrectly in the layout.

| improve this answer | |

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.