7

I maintain the maps for my local Nordic ski trails. Each winter during the 'Tour de Soup' skiers travel from one soup station to another with the aid of a special event map.

Because the map is already fairly busy I want to offset the soup station icons and link to their true locations with a label line. I am aware of the EasyCustomLabeling plugin but it is not suitable for my needs. EasyCustomLabeling only supports label positioning with a coordinate. That coordinate may be suitable for display at one scale, but zoom in or out and the label will disappear off-screen or start to overlap the feature. The Tour de Soup map includes an inset map, so in the same PDF there are two very different scales. My goal is to offset an icon using display units, e.g. millimetres, so that it displays consistently at multiple scales.

I have an approach that respects the scale of the map in the main QGIS window, using a custom function (below). If I zoom in or out in the main QGIS window the icons correctly maintain the same display offset distance. However this approach is not sensitive to the layout context.

If the main QGIS window's scale is similar to the layout's main map scale then the layout's main map displays correctly. However, because the layout's main and inset maps have different scales I cannot have both display correctly.

EDIT: David Galt's answer below shows how to determine the scale of the different maps in the layout, but this only solves half of the problem. I also need to know which map is currently rendering the feature so that I can choose the appropriate scale from those available. I find the API documentation difficult to navigate and cannot tell if what I want is possible.

Custom Function:

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

# displayOffsetStr: "100,-55" is 100 units X offset, -55 units Y offset
# conversionFactor: ratio of offset unit to coordinate system's distance unit, so 1000 gives mm to m conversion
@qgsfunction(args='auto', group='Custom')
def offset_point_by_display_distance(displayOffsetStr, conversionFactor, feature, parent):
    # *** this is where the main QGIS window's scale comes from
    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)
    else:
        return (QgsGeometry)(locationPoint)

This function is referenced by a Geometry Generator symbol rule with

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

The problem comes from iface.mapCanvas().scale() - that is what gives me the main QGIS window's scale instead of the scale of the map that is rendering the feature. The question is "how can I tell which map is currently rendering the feature for which my custom Python function is being called?"

This screenshot shows the current behaviour. The arrows identify the same feature in both the main and inset maps. As you can see the inset map's scale is incorrect.

screenshot showing problem scale behaviour

  • Is the new labels callouts in QGIS 3.10 helps you ? (source) – J. Monticolo Jan 6 at 9:15
  • @J.Monticolo maybe, I'll have to give it a try in the next day or two. In theory this could be exactly what I need – tomfumb Jan 6 at 18:09
0
+100

You should use context variable @map_scale which will give you the scale of the current map being rendered.


Also, worth noticing in recent QGIS, what are called labels callouts, which might solve your issue in an easier way:

enter image description here

|improve this answer|||||
  • @map_scale gives the same result as iface.mapCanvas().scale() so I assume it's simply an alias. However label callouts does the job. The question as asked is still unanswered but this gives the desired result so good enough! – tomfumb Jan 9 at 4:23
2
project = QgsProject.instance()
projectLayoutManager = project.layoutManager()
layout = projectLayoutManager.layoutByName("Name of your layout")
map = layout.itemById("map")
map.scale()
|improve this answer|||||
  • 1
    thanks, this is definitely progress but it won't solve my problem. If Feature 1 appears in both Map A and Map B it will be rendered twice. With this approach I can determine the scales of Map A and Map B but I cannot tell which of those maps is currently rendering the feature. The question I need to answer is "what is the scale of the map within which you are currently being rendered?" – tomfumb Dec 28 '19 at 22:53

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.