[{"data":1,"prerenderedAt":1591},["ShallowReactive",2],{"doc:\u002Fpyqgis-cartography-visualization\u002Fmap-canvas-and-image-export\u002Fadd-scalebar-and-north-arrow-pyqgis":3},{"id":4,"title":5,"body":6,"description":1580,"extension":1581,"meta":1582,"navigation":325,"path":1587,"seo":1588,"stem":1589,"__hash__":1590},"docs\u002Fpyqgis-cartography-visualization\u002Fmap-canvas-and-image-export\u002Fadd-scalebar-and-north-arrow-pyqgis\u002Findex.md","Add a Scale Bar and North Arrow to a Layout in PyQGIS",{"type":7,"value":8,"toc":1567},"minimark",[9,13,27,41,219,224,244,248,251,489,511,515,520,637,667,670,698,710,714,721,810,973,1000,1004,1007,1159,1176,1180,1183,1322,1325,1329,1335,1397,1405,1409,1463,1467,1482,1486,1496,1505,1511,1520,1526,1532,1538,1542,1563],[10,11,5],"h1",{"id":12},"add-a-scale-bar-and-north-arrow-to-a-layout-in-pyqgis",[14,15,16,17,21,22,26],"p",{},"A map without a scale bar is a picture. Adding one from Python is straightforward, but two things catch people out: a scale bar that is not ",[18,19,20],"em",{},"linked"," to a map item shows a meaningless default scale rather than the map's, and a north arrow is not a special item type at all — it is a picture item pointing at an SVG, which is why searching the API for ",[23,24,25],"code",{},"QgsLayoutItemNorthArrow"," finds nothing.",[14,28,29,30,35,36,40],{},"This page is a focused recipe within ",[31,32,34],"a",{"href":33},"\u002Fpyqgis-cartography-visualization\u002Fmap-canvas-and-image-export\u002F","Map Canvas Control and Image Export in PyQGIS",", and it builds on the layout automation in ",[31,37,39],{"href":38},"\u002Fspatial-data-processing-automation\u002Fautomated-map-layout-generation\u002F","Automated Map Layout Generation",". It covers building a layout with a map item, adding and linking a scale bar, adding a rotation-aware north arrow, and positioning both reliably.",[14,42,43],{},[44,45,50,54,58,65,82,91,101,106,116,121,125,130,135,163,182,188,192,197,202,206,209,215],"svg",{"viewBox":46,"role":47,"ariaLabel":48,"xmlns":49},"0 0 760 268","img","A print layout containing a map item with a scale bar and north arrow anchored to its lower corners, with arrows showing that both are linked to the map item rather than to the page","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[51,52,53],"title",{},"How the decorations attach to the map item",[55,56,57],"desc",{},"A layout page holds a map item occupying most of it. A scale bar sits in the lower left and a north arrow in the lower right. Dashed arrows from both point back at the map item, indicating that the scale bar reads its scale from the map and the north arrow reads its rotation, rather than either being independent of it.",[59,60],"rect",{"x":61,"y":61,"width":62,"height":63,"fill":64},"0","760","268","#f6f3ea",[66,67,68],"defs",{},[69,70,77],"marker",{"id":71,"viewBox":72,"refX":73,"refY":74,"markerWidth":75,"markerHeight":75,"orient":76},"layoutLinkArrow","0 0 10 10","8","5","7","auto-start-reverse",[78,79],"path",{"d":80,"fill":81},"M0 0 L10 5 L0 10 z","#b45309",[83,84,90],"text",{"x":85,"y":86,"style":87,"fill":88,"textAnchor":89},"380","26","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","Both decorations read from the map item — link them or they lie",[59,92],{"x":93,"y":94,"width":95,"height":96,"rx":97,"fill":98,"stroke":99,"style":100},"150","46","460","200","6","#fffdf7","#59645f","stroke-width:2",[83,102,105],{"x":85,"y":103,"style":104,"fill":99,"textAnchor":89},"66","text-anchor:middle;font-size:11px;font-family:sans-serif","A4 landscape page",[59,107],{"x":108,"y":109,"width":110,"height":111,"rx":112,"fill":113,"stroke":114,"style":115},"176","78","408","120","4","#e7e2d4","#0f766e","stroke-width:2.5",[78,117],{"d":118,"fill":114,"fillOpacity":119,"stroke":114,"style":120},"M206 178 L206 108 L272 100 L308 142 L268 182 Z",0.25,"stroke-width:1.8",[78,122],{"d":123,"fill":81,"fillOpacity":124,"stroke":81,"style":120},"M330 184 L326 116 L420 108 L432 176 Z",0.22,[78,126],{"d":127,"fill":128,"fillOpacity":129,"stroke":128,"style":120},"M460 180 L456 124 L548 118 L556 178 Z","#2563eb",0.2,[83,131,134],{"x":85,"y":132,"style":133,"fill":114,"textAnchor":89},"94","text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif","QgsLayoutItemMap",[136,137,138,144,149,153,156],"g",{},[59,139],{"x":108,"y":140,"width":93,"height":141,"rx":142,"fill":98,"stroke":143,"style":100},"208","28","3","#15803d",[59,145],{"x":146,"y":147,"width":148,"height":73,"fill":88},"186","218","30",[59,150],{"x":151,"y":147,"width":148,"height":73,"fill":98,"stroke":88,"style":152},"216","stroke-width:1",[59,154],{"x":155,"y":147,"width":148,"height":73,"fill":88},"246",[83,157,162],{"x":158,"y":159,"style":160,"fill":161,"textAnchor":89},"300","226","text-anchor:middle;font-size:9.5px;font-family:sans-serif","#2f3b35","1 km",[136,164,165,171,176],{},[59,166],{"x":167,"y":168,"width":169,"height":170,"rx":142,"fill":98,"stroke":128,"style":100},"512","204","72","36",[78,172],{"d":173,"fill":174,"stroke":88,"style":175},"M548 234 L548 210 M541 218 L548 208 L555 218","none","stroke-width:2;stroke-linejoin:round",[83,177,181],{"x":178,"y":179,"style":180,"fill":88,"textAnchor":89},"530","228","text-anchor:middle;font-size:10px;font-weight:bold;font-family:sans-serif","N",[183,184],"line",{"x1":185,"y1":186,"x2":185,"y2":96,"stroke":81,"style":187},"250","206","stroke-width:2;stroke-dasharray:4 3;marker-end:url(#layoutLinkArrow)",[183,189],{"x1":190,"y1":191,"x2":190,"y2":96,"stroke":81,"style":187},"548","202",[83,193,196],{"x":194,"y":195,"style":133,"fill":143,"textAnchor":89},"70","212","scale bar",[83,198,201],{"x":194,"y":199,"style":200,"fill":161,"textAnchor":89},"230","text-anchor:middle;font-size:10.5px;font-family:sans-serif","setLinkedMap()",[83,203,205],{"x":204,"y":195,"style":133,"fill":128,"textAnchor":89},"678","north arrow",[83,207,208],{"x":204,"y":199,"style":200,"fill":161,"textAnchor":89},"a picture item",[183,210],{"x1":211,"y1":212,"x2":213,"y2":212,"stroke":143,"style":214},"110","220","172","stroke-width:2;marker-end:url(#layoutLinkArrow)",[183,216],{"x1":217,"y1":212,"x2":218,"y2":212,"stroke":128,"style":214},"640","590",[220,221,223],"h2",{"id":222},"prerequisites","Prerequisites",[225,226,227,235,238],"ul",{},[228,229,230,234],"li",{},[231,232,233],"strong",{},"QGIS 3.34 LTR"," (bundled Python 3.12) or newer.",[228,236,237],{},"A project with at least one styled layer.",[228,239,240,241,243],{},"Familiarity with the layout API — ",[31,242,39],{"href":38}," covers creating layouts and exporting them.",[220,245,247],{"id":246},"build-a-layout-with-a-map-item","Build a layout with a map item",[14,249,250],{},"The scale bar needs something to link to, so the map item comes first.",[252,253,258],"pre",{"className":254,"code":255,"language":256,"meta":257,"style":257},"language-python shiki shiki-themes github-dark","from qgis.core import (\n    QgsLayout,\n    QgsLayoutItemMap,\n    QgsLayoutPoint,\n    QgsLayoutSize,\n    QgsProject,\n    QgsUnitTypes,\n)\n\nproject = QgsProject.instance()\nlayer = project.mapLayersByName(\"parcels\")[0]\n\nlayout = QgsLayout(project)\nlayout.initializeDefaults()\nlayout.setName(\"Parcels A4\")\n\nmap_item = QgsLayoutItemMap(layout)\nmap_item.attemptMove(QgsLayoutPoint(10, 10, QgsUnitTypes.LayoutMillimeters))\nmap_item.attemptResize(QgsLayoutSize(190, 110, QgsUnitTypes.LayoutMillimeters))\n\nextent = layer.extent()\nextent.scale(1.1)\nmap_item.setExtent(extent)\nmap_item.setLayers([layer])\nlayout.addLayoutItem(map_item)\n","python","",[23,259,260,278,284,290,296,302,308,314,320,327,339,363,368,379,385,396,401,412,429,444,449,460,471,477,483],{"__ignoreMap":257},[261,262,264,268,272,275],"span",{"class":183,"line":263},1,[261,265,267],{"class":266},"snl16","from",[261,269,271],{"class":270},"s95oV"," qgis.core ",[261,273,274],{"class":266},"import",[261,276,277],{"class":270}," (\n",[261,279,281],{"class":183,"line":280},2,[261,282,283],{"class":270},"    QgsLayout,\n",[261,285,287],{"class":183,"line":286},3,[261,288,289],{"class":270},"    QgsLayoutItemMap,\n",[261,291,293],{"class":183,"line":292},4,[261,294,295],{"class":270},"    QgsLayoutPoint,\n",[261,297,299],{"class":183,"line":298},5,[261,300,301],{"class":270},"    QgsLayoutSize,\n",[261,303,305],{"class":183,"line":304},6,[261,306,307],{"class":270},"    QgsProject,\n",[261,309,311],{"class":183,"line":310},7,[261,312,313],{"class":270},"    QgsUnitTypes,\n",[261,315,317],{"class":183,"line":316},8,[261,318,319],{"class":270},")\n",[261,321,323],{"class":183,"line":322},9,[261,324,326],{"emptyLinePlaceholder":325},true,"\n",[261,328,330,333,336],{"class":183,"line":329},10,[261,331,332],{"class":270},"project ",[261,334,335],{"class":266},"=",[261,337,338],{"class":270}," QgsProject.instance()\n",[261,340,342,345,347,350,354,357,360],{"class":183,"line":341},11,[261,343,344],{"class":270},"layer ",[261,346,335],{"class":266},[261,348,349],{"class":270}," project.mapLayersByName(",[261,351,353],{"class":352},"sU2Wk","\"parcels\"",[261,355,356],{"class":270},")[",[261,358,61],{"class":359},"sDLfK",[261,361,362],{"class":270},"]\n",[261,364,366],{"class":183,"line":365},12,[261,367,326],{"emptyLinePlaceholder":325},[261,369,371,374,376],{"class":183,"line":370},13,[261,372,373],{"class":270},"layout ",[261,375,335],{"class":266},[261,377,378],{"class":270}," QgsLayout(project)\n",[261,380,382],{"class":183,"line":381},14,[261,383,384],{"class":270},"layout.initializeDefaults()\n",[261,386,388,391,394],{"class":183,"line":387},15,[261,389,390],{"class":270},"layout.setName(",[261,392,393],{"class":352},"\"Parcels A4\"",[261,395,319],{"class":270},[261,397,399],{"class":183,"line":398},16,[261,400,326],{"emptyLinePlaceholder":325},[261,402,404,407,409],{"class":183,"line":403},17,[261,405,406],{"class":270},"map_item ",[261,408,335],{"class":266},[261,410,411],{"class":270}," QgsLayoutItemMap(layout)\n",[261,413,415,418,421,424,426],{"class":183,"line":414},18,[261,416,417],{"class":270},"map_item.attemptMove(QgsLayoutPoint(",[261,419,420],{"class":359},"10",[261,422,423],{"class":270},", ",[261,425,420],{"class":359},[261,427,428],{"class":270},", QgsUnitTypes.LayoutMillimeters))\n",[261,430,432,435,438,440,442],{"class":183,"line":431},19,[261,433,434],{"class":270},"map_item.attemptResize(QgsLayoutSize(",[261,436,437],{"class":359},"190",[261,439,423],{"class":270},[261,441,211],{"class":359},[261,443,428],{"class":270},[261,445,447],{"class":183,"line":446},20,[261,448,326],{"emptyLinePlaceholder":325},[261,450,452,455,457],{"class":183,"line":451},21,[261,453,454],{"class":270},"extent ",[261,456,335],{"class":266},[261,458,459],{"class":270}," layer.extent()\n",[261,461,463,466,469],{"class":183,"line":462},22,[261,464,465],{"class":270},"extent.scale(",[261,467,468],{"class":359},"1.1",[261,470,319],{"class":270},[261,472,474],{"class":183,"line":473},23,[261,475,476],{"class":270},"map_item.setExtent(extent)\n",[261,478,480],{"class":183,"line":479},24,[261,481,482],{"class":270},"map_item.setLayers([layer])\n",[261,484,486],{"class":183,"line":485},25,[261,487,488],{"class":270},"layout.addLayoutItem(map_item)\n",[14,490,491,494,495,498,499,502,503,506,507,510],{},[231,492,493],{},"Breakdown:"," ",[23,496,497],{},"initializeDefaults()"," creates a single A4 page, without which the layout has no page at all and every item lands nowhere. ",[23,500,501],{},"attemptMove()"," and ",[23,504,505],{},"attemptResize()"," are the correct positioning calls — they respect item constraints and reference points, which plain coordinate assignment does not. Positions and sizes carry an explicit unit, so millimetres and page coordinates cannot be confused. ",[23,508,509],{},"setLayers()"," on the map item pins the content; without it the item follows the project's layer tree and changes whenever a user ticks a box.",[220,512,514],{"id":513},"add-and-link-a-scale-bar","Add and link a scale bar",[14,516,517,519],{},[23,518,201],{}," is the whole trick. Everything else is presentation.",[252,521,523],{"className":254,"code":522,"language":256,"meta":257,"style":257},"from qgis.core import QgsLayoutItemScaleBar, QgsLayoutPoint, QgsUnitTypes\n\nbar = QgsLayoutItemScaleBar(layout)\nbar.setStyle(\"Single Box\")\nbar.setLinkedMap(map_item)          # must come before applyDefaultSize()\nbar.setUnits(QgsUnitTypes.DistanceKilometers)\nbar.setUnitsPerSegment(1)\nbar.setNumberOfSegments(4)\nbar.setNumberOfSegmentsLeft(0)\nbar.setUnitLabel(\"km\")\nbar.applyDefaultSize()\nbar.attemptMove(QgsLayoutPoint(12, 124, QgsUnitTypes.LayoutMillimeters))\nlayout.addLayoutItem(bar)\n",[23,524,525,536,540,550,560,569,574,584,593,602,612,617,632],{"__ignoreMap":257},[261,526,527,529,531,533],{"class":183,"line":263},[261,528,267],{"class":266},[261,530,271],{"class":270},[261,532,274],{"class":266},[261,534,535],{"class":270}," QgsLayoutItemScaleBar, QgsLayoutPoint, QgsUnitTypes\n",[261,537,538],{"class":183,"line":280},[261,539,326],{"emptyLinePlaceholder":325},[261,541,542,545,547],{"class":183,"line":286},[261,543,544],{"class":270},"bar ",[261,546,335],{"class":266},[261,548,549],{"class":270}," QgsLayoutItemScaleBar(layout)\n",[261,551,552,555,558],{"class":183,"line":292},[261,553,554],{"class":270},"bar.setStyle(",[261,556,557],{"class":352},"\"Single Box\"",[261,559,319],{"class":270},[261,561,562,565],{"class":183,"line":298},[261,563,564],{"class":270},"bar.setLinkedMap(map_item)          ",[261,566,568],{"class":567},"sjoCn","# must come before applyDefaultSize()\n",[261,570,571],{"class":183,"line":304},[261,572,573],{"class":270},"bar.setUnits(QgsUnitTypes.DistanceKilometers)\n",[261,575,576,579,582],{"class":183,"line":310},[261,577,578],{"class":270},"bar.setUnitsPerSegment(",[261,580,581],{"class":359},"1",[261,583,319],{"class":270},[261,585,586,589,591],{"class":183,"line":316},[261,587,588],{"class":270},"bar.setNumberOfSegments(",[261,590,112],{"class":359},[261,592,319],{"class":270},[261,594,595,598,600],{"class":183,"line":322},[261,596,597],{"class":270},"bar.setNumberOfSegmentsLeft(",[261,599,61],{"class":359},[261,601,319],{"class":270},[261,603,604,607,610],{"class":183,"line":329},[261,605,606],{"class":270},"bar.setUnitLabel(",[261,608,609],{"class":352},"\"km\"",[261,611,319],{"class":270},[261,613,614],{"class":183,"line":341},[261,615,616],{"class":270},"bar.applyDefaultSize()\n",[261,618,619,622,625,627,630],{"class":183,"line":365},[261,620,621],{"class":270},"bar.attemptMove(QgsLayoutPoint(",[261,623,624],{"class":359},"12",[261,626,423],{"class":270},[261,628,629],{"class":359},"124",[261,631,428],{"class":270},[261,633,634],{"class":183,"line":370},[261,635,636],{"class":270},"layout.addLayoutItem(bar)\n",[14,638,639,494,641,643,644,494,647,650,651,654,655,423,657,423,660,423,663,666],{},[231,640,493],{},[23,642,201],{}," connects the bar to the map item so it reads the real scale; a bar added without it shows a placeholder that has no relationship to the map and is worse than no bar at all. It must be called ",[18,645,646],{},"before",[23,648,649],{},"applyDefaultSize()",", which sizes the bar from the linked map's scale — the other order produces a bar of an arbitrary width. ",[23,652,653],{},"setNumberOfSegmentsLeft(0)"," removes the subdivided segment to the left of zero, which many cartographers prefer omitted. Style names are the same strings as the GUI dropdown: ",[23,656,557],{},[23,658,659],{},"\"Double Box\"",[23,661,662],{},"\"Line Ticks Up\"",[23,664,665],{},"\"Numeric\"",".",[14,668,669],{},"For a map series where the scale varies per page, let the bar size itself rather than fixing its width:",[252,671,673],{"className":254,"code":672,"language":256,"meta":257,"style":257},"bar.setSegmentSizeMode(QgsScaleBarSettings.SegmentSizeFitWidth)\nbar.setMinimumBarWidth(30)\nbar.setMaximumBarWidth(70)\n",[23,674,675,680,689],{"__ignoreMap":257},[261,676,677],{"class":183,"line":263},[261,678,679],{"class":270},"bar.setSegmentSizeMode(QgsScaleBarSettings.SegmentSizeFitWidth)\n",[261,681,682,685,687],{"class":183,"line":280},[261,683,684],{"class":270},"bar.setMinimumBarWidth(",[261,686,148],{"class":359},[261,688,319],{"class":270},[261,690,691,694,696],{"class":183,"line":286},[261,692,693],{"class":270},"bar.setMaximumBarWidth(",[261,695,194],{"class":359},[261,697,319],{"class":270},[14,699,700,494,702,705,706,666],{},[231,701,493],{},[23,703,704],{},"SegmentSizeFitWidth"," tells the bar to choose a round segment distance that fits the width range you allow, rather than holding a fixed distance per segment and growing or shrinking on the page. That is what keeps an atlas readable when one page is at 1:5 000 and the next at 1:25 000 — see ",[31,707,709],{"href":708},"\u002Fspatial-data-processing-automation\u002Fautomating-atlas-map-series\u002F","Automating Atlas Map Series",[220,711,713],{"id":712},"add-a-north-arrow","Add a north arrow",[14,715,716,717,720],{},"There is no north-arrow class. A north arrow is a ",[23,718,719],{},"QgsLayoutItemPicture"," pointing at one of the SVGs QGIS ships, with its rotation synchronised to the map.",[14,722,723],{},[44,724,727,730,733,736,739,764,786,807],{"viewBox":725,"role":47,"ariaLabel":726,"xmlns":49},"0 0 760 236","A north arrow shown at three map rotations, with the arrow rotating to compensate so it always points to true north",[51,728,729],{},"Why the arrow must follow the map rotation",[55,731,732],{},"Three map frames at rotations of zero, thirty and negative forty-five degrees. In each, the north arrow is drawn rotated by the opposite amount so that it continues to point at true north on the page rather than staying fixed to the frame.",[59,734],{"x":61,"y":61,"width":62,"height":735,"fill":64},"236",[83,737,738],{"x":85,"y":86,"style":87,"fill":88,"textAnchor":89},"setSyncWithMap() keeps the arrow honest when the map rotates",[136,740,741,745,750,755,761],{},[59,742],{"x":743,"y":94,"width":744,"height":93,"rx":420,"fill":98,"stroke":99,"style":100},"40","188",[59,746],{"x":194,"y":747,"width":748,"height":749,"fill":113,"stroke":114,"style":100},"84","128","80",[83,751,754],{"x":752,"y":194,"style":753,"fill":88,"textAnchor":89},"134","text-anchor:middle;font-size:11.5px;font-weight:bold;font-family:sans-serif","map rotation 0°",[136,756,757],{},[78,758],{"d":759,"fill":174,"stroke":88,"style":760},"M 178 164 L 178 138 M 171 147 L 178 136 L 185 147","stroke-width:2.2;stroke-linejoin:round",[83,762,763],{"x":752,"y":146,"style":104,"fill":161,"textAnchor":89},"arrow at 0°",[136,765,766,769,773,777,783],{},[59,767],{"x":768,"y":94,"width":744,"height":93,"rx":420,"fill":98,"stroke":99,"style":100},"248",[59,770],{"x":771,"y":747,"width":748,"height":749,"fill":113,"stroke":114,"style":100,"transform":772},"278","rotate(30 342 124)",[83,774,776],{"x":775,"y":194,"style":753,"fill":88,"textAnchor":89},"342","map rotation 30°",[136,778,780],{"transform":779},"translate(386,152) rotate(-30)",[78,781],{"d":782,"fill":174,"stroke":88,"style":760},"M0 12 L0 -14 M-7 -5 L0 -16 L7 -5",[83,784,785],{"x":775,"y":146,"style":104,"fill":161,"textAnchor":89},"arrow at −30°",[136,787,788,791,795,799,804],{},[59,789],{"x":790,"y":94,"width":744,"height":93,"rx":420,"fill":98,"stroke":99,"style":100},"456",[59,792],{"x":793,"y":747,"width":748,"height":749,"fill":113,"stroke":114,"style":100,"transform":794},"486","rotate(-45 550 124)",[83,796,798],{"x":797,"y":194,"style":753,"fill":88,"textAnchor":89},"550","map rotation −45°",[136,800,802],{"transform":801},"translate(594,152) rotate(45)",[78,803],{"d":782,"fill":174,"stroke":88,"style":760},[83,805,806],{"x":797,"y":146,"style":104,"fill":161,"textAnchor":89},"arrow at +45°",[83,808,809],{"x":85,"y":212,"style":104,"fill":99,"textAnchor":89},"an arrow fixed to the frame points north only by coincidence",[252,811,813],{"className":254,"code":812,"language":256,"meta":257,"style":257},"from qgis.core import (\n    QgsApplication,\n    QgsLayoutItemPicture,\n    QgsLayoutPoint,\n    QgsLayoutSize,\n    QgsUnitTypes,\n)\nfrom pathlib import Path\n\nsvg_root = Path(QgsApplication.pkgDataPath()) \u002F \"svg\" \u002F \"arrows\"\narrow_svg = svg_root \u002F \"NorthArrow_02.svg\"\n\narrow = QgsLayoutItemPicture(layout)\narrow.setPicturePath(str(arrow_svg))\narrow.setLinkedMap(map_item)\narrow.setNorthMode(QgsLayoutItemPicture.GridNorth)\narrow.attemptResize(QgsLayoutSize(16, 16, QgsUnitTypes.LayoutMillimeters))\narrow.attemptMove(QgsLayoutPoint(182, 122, QgsUnitTypes.LayoutMillimeters))\nlayout.addLayoutItem(arrow)\n",[23,814,815,825,830,835,839,843,847,851,863,867,889,904,908,918,929,934,939,953,968],{"__ignoreMap":257},[261,816,817,819,821,823],{"class":183,"line":263},[261,818,267],{"class":266},[261,820,271],{"class":270},[261,822,274],{"class":266},[261,824,277],{"class":270},[261,826,827],{"class":183,"line":280},[261,828,829],{"class":270},"    QgsApplication,\n",[261,831,832],{"class":183,"line":286},[261,833,834],{"class":270},"    QgsLayoutItemPicture,\n",[261,836,837],{"class":183,"line":292},[261,838,295],{"class":270},[261,840,841],{"class":183,"line":298},[261,842,301],{"class":270},[261,844,845],{"class":183,"line":304},[261,846,313],{"class":270},[261,848,849],{"class":183,"line":310},[261,850,319],{"class":270},[261,852,853,855,858,860],{"class":183,"line":316},[261,854,267],{"class":266},[261,856,857],{"class":270}," pathlib ",[261,859,274],{"class":266},[261,861,862],{"class":270}," Path\n",[261,864,865],{"class":183,"line":322},[261,866,326],{"emptyLinePlaceholder":325},[261,868,869,872,874,877,880,883,886],{"class":183,"line":329},[261,870,871],{"class":270},"svg_root ",[261,873,335],{"class":266},[261,875,876],{"class":270}," Path(QgsApplication.pkgDataPath()) ",[261,878,879],{"class":266},"\u002F",[261,881,882],{"class":352}," \"svg\"",[261,884,885],{"class":266}," \u002F",[261,887,888],{"class":352}," \"arrows\"\n",[261,890,891,894,896,899,901],{"class":183,"line":341},[261,892,893],{"class":270},"arrow_svg ",[261,895,335],{"class":266},[261,897,898],{"class":270}," svg_root ",[261,900,879],{"class":266},[261,902,903],{"class":352}," \"NorthArrow_02.svg\"\n",[261,905,906],{"class":183,"line":365},[261,907,326],{"emptyLinePlaceholder":325},[261,909,910,913,915],{"class":183,"line":370},[261,911,912],{"class":270},"arrow ",[261,914,335],{"class":266},[261,916,917],{"class":270}," QgsLayoutItemPicture(layout)\n",[261,919,920,923,926],{"class":183,"line":381},[261,921,922],{"class":270},"arrow.setPicturePath(",[261,924,925],{"class":359},"str",[261,927,928],{"class":270},"(arrow_svg))\n",[261,930,931],{"class":183,"line":387},[261,932,933],{"class":270},"arrow.setLinkedMap(map_item)\n",[261,935,936],{"class":183,"line":398},[261,937,938],{"class":270},"arrow.setNorthMode(QgsLayoutItemPicture.GridNorth)\n",[261,940,941,944,947,949,951],{"class":183,"line":403},[261,942,943],{"class":270},"arrow.attemptResize(QgsLayoutSize(",[261,945,946],{"class":359},"16",[261,948,423],{"class":270},[261,950,946],{"class":359},[261,952,428],{"class":270},[261,954,955,958,961,963,966],{"class":183,"line":414},[261,956,957],{"class":270},"arrow.attemptMove(QgsLayoutPoint(",[261,959,960],{"class":359},"182",[261,962,423],{"class":270},[261,964,965],{"class":359},"122",[261,967,428],{"class":270},[261,969,970],{"class":183,"line":431},[261,971,972],{"class":270},"layout.addLayoutItem(arrow)\n",[14,974,975,494,977,980,981,984,985,987,988,991,992,995,996,999],{},[231,976,493],{},[23,978,979],{},"QgsApplication.pkgDataPath()"," locates the QGIS installation's shared data so the SVG path works on any machine — hard-coding ",[23,982,983],{},"\u002Fusr\u002Fshare\u002Fqgis\u002Fsvg\u002F..."," breaks the moment the script moves to Windows. ",[23,986,201],{}," plus ",[23,989,990],{},"setNorthMode()"," is what makes the arrow rotate to compensate when the map item is rotated; ",[23,993,994],{},"GridNorth"," follows the CRS grid, ",[23,997,998],{},"TrueNorth"," follows the meridian, and the two diverge noticeably at high latitudes. Without the link the arrow is decoration that happens to point up.",[220,1001,1003],{"id":1002},"register-and-export","Register and export",[14,1005,1006],{},"A layout has to be added to the project's layout manager before it can be found by name or exported by the GUI.",[252,1008,1010],{"className":254,"code":1009,"language":256,"meta":257,"style":257},"from qgis.core import QgsLayoutExporter\n\nmanager = project.layoutManager()\nexisting = manager.layoutByName(layout.name())\nif existing:\n    manager.removeLayout(existing)\nmanager.addLayout(layout)\n\nexporter = QgsLayoutExporter(layout)\nsettings = QgsLayoutExporter.PdfExportSettings()\nsettings.dpi = 300\nresult = exporter.exportToPdf(\"\u002Fdata\u002Fexports\u002Fparcels.pdf\", settings)\nif result != QgsLayoutExporter.Success:\n    raise RuntimeError(f\"export failed with code {result}\")\n",[23,1011,1012,1023,1027,1037,1047,1055,1060,1065,1069,1079,1089,1099,1115,1128],{"__ignoreMap":257},[261,1013,1014,1016,1018,1020],{"class":183,"line":263},[261,1015,267],{"class":266},[261,1017,271],{"class":270},[261,1019,274],{"class":266},[261,1021,1022],{"class":270}," QgsLayoutExporter\n",[261,1024,1025],{"class":183,"line":280},[261,1026,326],{"emptyLinePlaceholder":325},[261,1028,1029,1032,1034],{"class":183,"line":286},[261,1030,1031],{"class":270},"manager ",[261,1033,335],{"class":266},[261,1035,1036],{"class":270}," project.layoutManager()\n",[261,1038,1039,1042,1044],{"class":183,"line":292},[261,1040,1041],{"class":270},"existing ",[261,1043,335],{"class":266},[261,1045,1046],{"class":270}," manager.layoutByName(layout.name())\n",[261,1048,1049,1052],{"class":183,"line":298},[261,1050,1051],{"class":266},"if",[261,1053,1054],{"class":270}," existing:\n",[261,1056,1057],{"class":183,"line":304},[261,1058,1059],{"class":270},"    manager.removeLayout(existing)\n",[261,1061,1062],{"class":183,"line":310},[261,1063,1064],{"class":270},"manager.addLayout(layout)\n",[261,1066,1067],{"class":183,"line":316},[261,1068,326],{"emptyLinePlaceholder":325},[261,1070,1071,1074,1076],{"class":183,"line":322},[261,1072,1073],{"class":270},"exporter ",[261,1075,335],{"class":266},[261,1077,1078],{"class":270}," QgsLayoutExporter(layout)\n",[261,1080,1081,1084,1086],{"class":183,"line":329},[261,1082,1083],{"class":270},"settings ",[261,1085,335],{"class":266},[261,1087,1088],{"class":270}," QgsLayoutExporter.PdfExportSettings()\n",[261,1090,1091,1094,1096],{"class":183,"line":341},[261,1092,1093],{"class":270},"settings.dpi ",[261,1095,335],{"class":266},[261,1097,1098],{"class":359}," 300\n",[261,1100,1101,1104,1106,1109,1112],{"class":183,"line":365},[261,1102,1103],{"class":270},"result ",[261,1105,335],{"class":266},[261,1107,1108],{"class":270}," exporter.exportToPdf(",[261,1110,1111],{"class":352},"\"\u002Fdata\u002Fexports\u002Fparcels.pdf\"",[261,1113,1114],{"class":270},", settings)\n",[261,1116,1117,1119,1122,1125],{"class":183,"line":370},[261,1118,1051],{"class":266},[261,1120,1121],{"class":270}," result ",[261,1123,1124],{"class":266},"!=",[261,1126,1127],{"class":270}," QgsLayoutExporter.Success:\n",[261,1129,1130,1133,1136,1139,1142,1145,1148,1151,1154,1157],{"class":183,"line":381},[261,1131,1132],{"class":266},"    raise",[261,1134,1135],{"class":359}," RuntimeError",[261,1137,1138],{"class":270},"(",[261,1140,1141],{"class":266},"f",[261,1143,1144],{"class":352},"\"export failed with code ",[261,1146,1147],{"class":359},"{",[261,1149,1150],{"class":270},"result",[261,1152,1153],{"class":359},"}",[261,1155,1156],{"class":352},"\"",[261,1158,319],{"class":270},[14,1160,1161,1163,1164,1167,1168,1171,1172,666],{},[231,1162,493],{}," Removing an existing layout of the same name first makes the script re-runnable; adding a duplicate name leaves two layouts and the GUI shows both. ",[23,1165,1166],{},"exportToPdf()"," returns a status code rather than raising, so comparing it against ",[23,1169,1170],{},"Success"," is the only way to catch a locked output file. For multi-page output and atlas driving, see ",[31,1173,1175],{"href":1174},"\u002Fspatial-data-processing-automation\u002Fautomated-map-layout-generation\u002Fexporting-multiple-qgis-layouts-to-pdf\u002F","Exporting Multiple QGIS Layouts to PDF",[220,1177,1179],{"id":1178},"pick-a-scale-bar-style-that-suits-the-map","Pick a scale bar style that suits the map",[14,1181,1182],{},"The style is not decoration — each one communicates a different level of precision, and a mismatch undermines the map's credibility.",[14,1184,1185],{},[44,1186,1189,1192,1195,1198,1201,1239,1279,1305],{"viewBox":1187,"role":47,"ariaLabel":1188,"xmlns":49},"0 0 760 244","Four scale bar styles compared: single box, double box, line ticks up and numeric, each annotated with the kind of map it suits",[51,1190,1191],{},"Four scale bar styles and what each implies",[55,1193,1194],{},"Single box draws alternating filled and empty segments and reads as a general-purpose bar. Double box stacks two rows of alternating segments, implying finer subdivision. Line ticks up draws a plain line with upward ticks, which suits technical drawings. Numeric prints the scale as a ratio, which suits fixed-scale output where a graphic bar would be redundant.",[59,1196],{"x":61,"y":61,"width":62,"height":1197,"fill":64},"244",[83,1199,1200],{"x":85,"y":86,"style":87,"fill":88,"textAnchor":89},"The style tells the reader how precise the map claims to be",[136,1202,1203,1207,1211,1230,1235],{},[59,1204],{"x":946,"y":94,"width":1205,"height":1206,"rx":420,"fill":98,"stroke":114,"style":100},"356","86",[83,1208,1210],{"x":170,"y":194,"style":1209,"fill":114},"font-size:11.5px;font-weight:bold;font-family:sans-serif","Single Box",[136,1212,1213,1216,1219,1222,1225],{},[59,1214],{"x":170,"y":1215,"width":743,"height":420,"fill":88},"82",[59,1217],{"x":1218,"y":1215,"width":743,"height":420,"fill":98,"stroke":88,"style":152},"76",[59,1220],{"x":1221,"y":1215,"width":743,"height":420,"fill":88},"116",[59,1223],{"x":1224,"y":1215,"width":743,"height":420,"fill":98,"stroke":88,"style":152},"156",[83,1226,1229],{"x":1227,"y":1228,"style":160,"fill":161,"textAnchor":89},"196","106","4 km",[83,1231,1234],{"x":199,"y":1232,"style":1233,"fill":161},"90","font-size:11px;font-family:sans-serif","general purpose",[83,1236,1238],{"x":199,"y":1237,"style":1233,"fill":99},"108","the safe default",[136,1240,1241,1244,1247,1272,1276],{},[59,1242],{"x":1243,"y":94,"width":1205,"height":1206,"rx":420,"fill":98,"stroke":128,"style":100},"388",[83,1245,1246],{"x":110,"y":194,"style":1209,"fill":128},"Double Box",[136,1248,1249,1251,1254,1257,1260,1263,1265,1267,1269],{},[59,1250],{"x":110,"y":109,"width":743,"height":75,"fill":88},[59,1252],{"x":1253,"y":109,"width":743,"height":75,"fill":98,"stroke":88,"style":152},"448",[59,1255],{"x":1256,"y":109,"width":743,"height":75,"fill":88},"488",[59,1258],{"x":1259,"y":109,"width":743,"height":75,"fill":98,"stroke":88,"style":152},"528",[59,1261],{"x":110,"y":1262,"width":743,"height":75,"fill":98,"stroke":88,"style":152},"85",[59,1264],{"x":1253,"y":1262,"width":743,"height":75,"fill":88},[59,1266],{"x":1256,"y":1262,"width":743,"height":75,"fill":98,"stroke":88,"style":152},[59,1268],{"x":1259,"y":1262,"width":743,"height":75,"fill":88},[83,1270,1229],{"x":1271,"y":1228,"style":160,"fill":161,"textAnchor":89},"568",[83,1273,1275],{"x":1274,"y":1232,"style":1233,"fill":161},"602","implies fine detail",[83,1277,1278],{"x":1274,"y":1237,"style":1233,"fill":99},"large-scale maps",[136,1280,1281,1284,1288,1299,1302],{},[59,1282],{"x":946,"y":1283,"width":1205,"height":1206,"rx":420,"fill":98,"stroke":81,"style":100},"144",[83,1285,1287],{"x":170,"y":1286,"style":1209,"fill":81},"168","Line Ticks Up",[136,1289,1290,1294,1297],{},[78,1291],{"d":1292,"fill":174,"stroke":88,"style":1293},"M36 190 L196 190","stroke-width:1.6",[78,1295],{"d":1296,"fill":174,"stroke":88,"style":1293},"M36 190 L36 178 M76 190 L76 182 M116 190 L116 178 M156 190 L156 182 M196 190 L196 178",[83,1298,1229],{"x":1227,"y":140,"style":160,"fill":161,"textAnchor":89},[83,1300,1301],{"x":199,"y":744,"style":1233,"fill":161},"technical drawings",[83,1303,1304],{"x":199,"y":186,"style":1233,"fill":99},"minimal ink",[136,1306,1307,1309,1312,1316,1319],{},[59,1308],{"x":1243,"y":1283,"width":1205,"height":1206,"rx":420,"fill":98,"stroke":143,"style":100},[83,1310,1311],{"x":110,"y":1286,"style":1209,"fill":143},"Numeric",[83,1313,1315],{"x":110,"y":1227,"style":1314,"fill":88},"font-size:15px;font-weight:bold;font-family:sans-serif","1 : 25 000",[83,1317,1318],{"x":1274,"y":744,"style":1233,"fill":161},"fixed-scale output only",[83,1320,1321],{"x":1274,"y":186,"style":1233,"fill":99},"meaningless if resized",[14,1323,1324],{},"The numeric style deserves a warning: a printed ratio is only true at the size the map was exported at. Resize the PDF, or view it on a screen at any zoom, and the stated scale becomes a lie — which is precisely why a graphic bar is the safer choice for anything that might be reproduced at a different size.",[220,1326,1328],{"id":1327},"qgis-version-compatibility","QGIS version compatibility",[14,1330,1331,1332,1334],{},"The examples target ",[231,1333,233],{}," (Python 3.12).",[1336,1337,1338,1354],"table",{},[1339,1340,1341],"thead",{},[1342,1343,1344,1348,1351],"tr",{},[1345,1346,1347],"th",{},"QGIS version",[1345,1349,1350],{},"Python",[1345,1352,1353],{},"Notes",[1355,1356,1357,1369,1380],"tbody",{},[1342,1358,1359,1363,1366],{},[1360,1361,1362],"td",{},"3.28 LTR",[1360,1364,1365],{},"3.9",[1360,1367,1368],{},"Identical API. Scale bar style names unchanged.",[1342,1370,1371,1374,1377],{},[1360,1372,1373],{},"3.34 LTR",[1360,1375,1376],{},"3.12",[1360,1378,1379],{},"Baseline for this page.",[1342,1381,1382,1385,1387],{},[1360,1383,1384],{},"3.40 \u002F 3.44",[1360,1386,1376],{},[1360,1388,1389,1392,1393,1396],{},[23,1390,1391],{},"Qgis.DistanceUnit"," replaces ",[23,1394,1395],{},"QgsUnitTypes.DistanceKilometers","; the old spelling still resolves.",[14,1398,1399,423,1402,1404],{},[23,1400,1401],{},"QgsLayoutItemScaleBar",[23,1403,719],{}," and the layout item positioning calls are unchanged across 3.x.",[220,1406,1408],{"id":1407},"troubleshooting","Troubleshooting",[225,1410,1411,1419,1430,1439,1449,1457],{},[228,1412,1413,494,1416,1418],{},[231,1414,1415],{},"The scale bar shows a nonsensical distance.",[23,1417,201],{}," was not called, so the bar has no map to read a scale from.",[228,1420,1421,494,1424,1426,1427,1429],{},[231,1422,1423],{},"The bar is a strange width.",[23,1425,649],{}," ran before ",[23,1428,201],{},". Link first, then size.",[228,1431,1432,1435,1436,1438],{},[231,1433,1434],{},"The north arrow is missing."," The SVG path is wrong. Build it from ",[23,1437,979],{}," rather than hard-coding an absolute path.",[228,1440,1441,494,1444,502,1446,1448],{},[231,1442,1443],{},"The arrow does not rotate with the map.",[23,1445,201],{},[23,1447,990],{}," were not both set on the picture item.",[228,1450,1451,494,1454,1456],{},[231,1452,1453],{},"Items appear off the page.",[23,1455,497],{}," was skipped, so the layout has no page. Add it before positioning anything.",[228,1458,1459,1462],{},[231,1460,1461],{},"Two layouts with the same name appear."," The script was re-run without removing the previous layout from the manager.",[220,1464,1466],{"id":1465},"conclusion","Conclusion",[14,1468,1469,1470,1472,1473,1475,1476,1478,1479,1481],{},"A scale bar and a north arrow are both ",[18,1471,20],{}," items: the bar reads its scale from the map item and the arrow reads its rotation. Call ",[23,1474,201],{}," on both — before ",[23,1477,649],{}," on the bar — build SVG paths from ",[23,1480,979],{},", and remove any same-named layout before adding yours so the script stays re-runnable.",[220,1483,1485],{"id":1484},"frequently-asked-questions","Frequently Asked Questions",[14,1487,1488,1491,1492,1495],{},[231,1489,1490],{},"Why does my scale bar show the wrong distance?","\nIt is not linked to a map item, so it has no scale to read and falls back to a default. Call ",[23,1493,1494],{},"bar.setLinkedMap(map_item)"," before doing anything else with it.",[14,1497,1498,1501,1502,1504],{},[231,1499,1500],{},"Is there a north arrow layout item?","\nNo. A north arrow is a ",[23,1503,719],{}," pointing at one of the arrow SVGs QGIS ships. Linking it to the map and setting a north mode is what makes it rotate correctly.",[14,1506,1507,1510],{},[231,1508,1509],{},"What is the difference between grid north and true north?","\nGrid north follows the CRS grid's vertical axis; true north follows the meridian towards the pole. They coincide along a projection's central meridian and diverge increasingly away from it, particularly at high latitudes.",[14,1512,1513,1516,1517,1519],{},[231,1514,1515],{},"How do I keep the scale bar readable across an atlas?","\nUse ",[23,1518,704],{}," with a minimum and maximum bar width. The bar then chooses a round segment distance that fits the allowed width instead of growing or shrinking as the page scale changes.",[14,1521,1522,1525],{},[231,1523,1524],{},"Should a small-scale map have a north arrow at all?","\nOften not. On a continental or world map the meridians converge visibly, so a single arrow is only correct at one point on the page and misleading everywhere else. A graticule communicates orientation honestly at those scales; reserve the arrow for maps small enough that north is effectively constant across the frame.",[14,1527,1528,1531],{},[231,1529,1530],{},"Can I put the scale bar inside the map frame?","\nYes — layout items are positioned independently, so placing the bar's coordinates within the map item's rectangle overlays it. Add a semi-opaque background to the bar so it stays legible over whatever the map draws beneath it.",[14,1533,1534,1537],{},[231,1535,1536],{},"Can the scale bar units follow the project?","\nYes — leaving the units unset lets the bar adopt the linked map's units, which keeps a template correct when it is reused on data in a different CRS. Setting them explicitly is better when the output must always read in kilometres regardless of what the data uses.",[220,1539,1541],{"id":1540},"related","Related",[225,1543,1544,1549,1554,1558],{},[228,1545,1546,1548],{},[31,1547,34],{"href":33}," — the guide this recipe belongs to",[228,1550,1551],{},[31,1552,1553],{"href":38},"Automated Map Layout Generation in PyQGIS",[228,1555,1556],{},[31,1557,1175],{"href":1174},[228,1559,1560],{},[31,1561,1562],{"href":708},"Automating Atlas Map Series in PyQGIS",[1564,1565,1566],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}",{"title":257,"searchDepth":280,"depth":280,"links":1568},[1569,1570,1571,1572,1573,1574,1575,1576,1577,1578,1579],{"id":222,"depth":280,"text":223},{"id":246,"depth":280,"text":247},{"id":513,"depth":280,"text":514},{"id":712,"depth":280,"text":713},{"id":1002,"depth":280,"text":1003},{"id":1178,"depth":280,"text":1179},{"id":1327,"depth":280,"text":1328},{"id":1407,"depth":280,"text":1408},{"id":1465,"depth":280,"text":1466},{"id":1484,"depth":280,"text":1485},{"id":1540,"depth":280,"text":1541},"Add a scale bar and north arrow to a QGIS print layout from Python. Link items to the map, choose units and segments, and position them reliably before export.","md",{"slug":1583,"type":1584,"breadcrumb":1585,"datePublished":1586,"dateModified":1586},"add-scalebar-and-north-arrow-pyqgis","article","Scale Bar & North Arrow","2026-08-01","\u002Fpyqgis-cartography-visualization\u002Fmap-canvas-and-image-export\u002Fadd-scalebar-and-north-arrow-pyqgis",{"title":5,"description":1580},"pyqgis-cartography-visualization\u002Fmap-canvas-and-image-export\u002Fadd-scalebar-and-north-arrow-pyqgis\u002Findex","pQ3k-Oi735ZN5Jz8pevuo4qmbyEsLpyrs-1noDU7i6E",1785585990471]