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.