3

Recently I’ve been creating a layout for my colleagues. Because they are likely to produce maps of various scales, I wanted to implement in the layout a scale bar that could automatically change from kilometers to meters depending on the canvas zoom.

The kind of errors, which I want to avoid is like the one below :

enter image description here

The scale bar is set to kilometers, but because the zoom is at a neighbourhood/plot scale, you can’t simply read the values. A scale bar set to meters would be much more appropriate :

enter image description here

Once again, I would like to see these changes in the scale bar settings being operated automatically by the software.

Do you think that expressions like : layer_property( 'layername', 'extent') bounds_width(geom), bounds_height(geom), map_extent_width, map_extent_height could be helpful ?

I noticed that the canevas scale bar was able of such performances (View --> Decorations --> Scale bar).
Here is the result when I zoom at a neighbourhood scale (left corner):

enter image description here

And the result when I zoom at a city scale :

enter image description here

I am using QGIS 3.10.8, the projection is always Lambert 93.


Now that I've been pinpointed the reason of the SyntaxError: invalid syntax, I'm carrying on with the well-indented script.

manager = QgsProject.instance().layoutManager() #Returns the project’s layout manager, which manages compositions within the project.
layout = manager.layoutByName("carte_siare_type_A4_paysage")
mapItem = layout.itemById("Map 1")
scalebar = layout.itemById("Scalebar")

def map_extent_changed():    
    if mapItem.scale()>= threshold:        
        scalebar.setUnits(1) # 1: kilometer
        scalebar.setUnitLabel("km")
    else:
        scalebar.setUnits(0) # 0: meter
        scalebar.setUnitLabel("m")
        
    mapItem.extentChanged.connect(map_extent_changed)

The result returned when I exectue the command is the following:

exec(open('C:/Users/tec21/AppData/Local/Temp/tmp7l0qm5b8.py'.encode('utf-8')).read()

It seems like the code is working. I have no warning messages in the console. However, when I open the layout and see if there are changes operating in the scale bar when I zoom to bigger and smaller scales that 1/50 000, nothing happens, the measure remains in kilometers, which is the default setting, whatever the zoom is.

8
  • It's reasonably easy with PyQGIS, but it means running a script in your project. I'm not sure how portable/user-friendly that is if you are making a template for others to use.
    – Matt
    Commented Feb 12, 2022 at 10:22
  • 5
    You can use the script in this answer. Commented Feb 12, 2022 at 11:51
  • Thank you both for your comments. So far I've never been coding very much with PyQgis, nor python. But let's give it a try. Once I'll have achieved this goal, I will decide whether use it or not for my colleagues. Kadir, do you mind helping again ? I copied/pasted your code, which I had modified to suit the proper names of my layouts, maps,... but I'm stuck with scalebar.setUnits(1). In the main post is the whole code :
    – Remi.M
    Commented Feb 14, 2022 at 16:42
  • 1
    Indentation is important in python. As in the linked answer and as the error message is saying, you need to indent scalebar.setUnits(1) to the right of the preceding if statement
    – JGH
    Commented Feb 17, 2022 at 13:15
  • Possible workarounds that are much less elegant than PyQGIS would be to 1: insert two scale bars in your layout, one in meters and one in km, and then have the user drag the one they want into the actual layout and the other out of it, or 2: create one layout for scales that kilometers work best and one for where meters work best.
    – John
    Commented Feb 17, 2022 at 14:12

1 Answer 1

1

You forgot threshold, and mapItem.extentChanged shouldn't be in method definition block.
Use like this:

threshold = 50000 # 1:50.000
    
manager = QgsProject.instance().layoutManager() 
layout = manager.layoutByName("carte_siare_type_A4_paysage")
mapItem = layout.itemById("Map 1")
scalebar = layout.itemById("Scalebar")

def map_extent_changed():    
    if mapItem.scale()>= threshold:        
        scalebar.setUnits(1) # 1: kilometer
        scalebar.setUnitLabel("km")
    else:
        scalebar.setUnits(0) # 0: meter
        scalebar.setUnitLabel("m")

mapItem.extentChanged.connect(map_extent_changed)
5
  • I haven't tested in Windows yet. Commented Feb 18, 2022 at 21:14
  • Thank you Kadir, I miscopied/mispasted the code in my previous edit. I can assure you that Threshold was already part of my code. However like Matt and you stressed it, mapItem.extentChanged was not properly indented. I brought that correction too. Now, I'm facing an other error: AttributeError: 'NonType' object has no attribute 'extentChanged'
    – Remi.M
    Commented Feb 21, 2022 at 16:55
  • Is there a map item named "Map 1" in the layout? Commented Feb 21, 2022 at 16:56
  • I believe I should give a "definition" to extentChanged, like you did for mapItem, scalebar,.. so far I can't think of something else required. I mean we have the project, the layout manager, the different layout manager items. By the way, I should emphasise that I'm working with Windows11.
    – Remi.M
    Commented Feb 21, 2022 at 17:00
  • Yes, it is the map itself.
    – Remi.M
    Commented Feb 21, 2022 at 17:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.