[{"data":1,"prerenderedAt":1275},["ShallowReactive",2],{"doc:\u002Fpyqgis-cartography-visualization\u002Flabeling-and-annotations\u002Fcontrol-label-placement-and-collisions-pyqgis":3},{"id":4,"title":5,"body":6,"description":1264,"extension":1265,"meta":1266,"navigation":272,"path":1271,"seo":1272,"stem":1273,"__hash__":1274},"docs\u002Fpyqgis-cartography-visualization\u002Flabeling-and-annotations\u002Fcontrol-label-placement-and-collisions-pyqgis\u002Findex.md","Control Label Placement and Collisions in PyQGIS",{"type":7,"value":8,"toc":1250},"minimark",[9,13,17,26,199,204,226,230,233,381,426,430,467,477,481,508,517,625,629,723,736,740,743,829,841,949,953,956,966,975,981,987,997,1000,1004,1081,1101,1105,1158,1162,1168,1172,1178,1184,1190,1196,1205,1215,1219,1246],[10,11,5],"h1",{"id":12},"control-label-placement-and-collisions-in-pyqgis",[14,15,16],"p",{},"Labelling is the part of automated cartography that most often looks wrong, and almost always for the same reason: QGIS could not fit every label, so it dropped some — silently, and not the ones you would have dropped. The engine is doing exactly what it was told. Telling it something better is a handful of properties: where labels may sit, which features matter more, what counts as an obstacle, and when overlapping is preferable to disappearing.",[14,18,19,20,25],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fpyqgis-cartography-visualization\u002Flabeling-and-annotations\u002F","Labeling and Annotations",". It covers placement modes per geometry type, priority, obstacles, allowing overlap deliberately, callout lines for crowded areas, and how to find out why a specific label is missing.",[14,27,28],{},[29,30,35,39,43,50,67,76,86,92,98,107,110,113,115,120,124,129,133,141,145,148,152,159,163,170,174,177,181,184,190,194],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"xmlns":34},"0 0 760 282","img","Diagram of the labelling engine deciding placement, showing candidate positions around a point, obstacles that block them, and the priority that decides which label survives a collision","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[36,37,38],"title",{},"How the engine decides",[40,41,42],"desc",{},"For each feature the engine generates candidate label positions. Candidates that overlap an obstacle or an already-placed label are discarded. If no candidate survives, the label is not drawn at all unless overlap is explicitly allowed. Priority decides which of two competing labels is placed first and therefore which one survives.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","282","#f6f3ea",[51,52,53],"defs",{},[54,55,62],"marker",{"id":56,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"labArrow","0 0 10 10","8","5","7","auto-start-reverse",[63,64],"path",{"d":65,"fill":66},"M0 0 L10 5 L0 10 z","#2f3b35",[68,69,75],"text",{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"380","28","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","Every label that does not fit is simply not drawn",[44,77],{"x":78,"y":79,"width":80,"height":81,"rx":82,"fill":83,"stroke":84,"style":85},"24","56","212","180","10","#fffdf7","#59645f","stroke-width:2",[68,87,91],{"x":88,"y":89,"style":90,"fill":73,"textAnchor":74},"130","82","text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif","candidates",[93,94],"circle",{"cx":88,"cy":95,"r":96,"fill":97},"150","6","#0f766e",[44,99],{"x":100,"y":101,"width":102,"height":103,"rx":104,"fill":105,"stroke":97,"style":106},"146","120","66","20","3","#eef7f4","stroke-width:1.2",[44,108],{"x":100,"y":109,"width":102,"height":103,"rx":104,"fill":105,"stroke":97,"style":106},"144",[44,111],{"x":112,"y":101,"width":102,"height":103,"rx":104,"fill":105,"stroke":97,"style":106},"48",[44,114],{"x":112,"y":109,"width":102,"height":103,"rx":104,"fill":105,"stroke":97,"style":106},[68,116,119],{"x":88,"y":117,"style":118,"fill":84,"textAnchor":74},"196","text-anchor:middle;font-size:10px;font-family:sans-serif","several positions tried",[68,121,123],{"x":88,"y":122,"style":118,"fill":84,"textAnchor":74},"214","in preference order",[44,125],{"x":126,"y":79,"width":80,"height":81,"rx":82,"fill":127,"stroke":128,"style":85},"272","#fdf2e2","#b45309",[68,130,132],{"x":131,"y":89,"style":90,"fill":128,"textAnchor":74},"378","obstacles",[44,134],{"x":135,"y":136,"width":137,"height":138,"rx":139,"fill":49,"stroke":128,"style":140},"296","104","164","46","4","stroke-width:1.5",[68,142,144],{"x":131,"y":143,"style":118,"fill":66,"textAnchor":74},"132","another layer's features",[44,146],{"x":135,"y":147,"width":137,"height":138,"rx":139,"fill":49,"stroke":128,"style":140},"158",[68,149,151],{"x":131,"y":150,"style":118,"fill":66,"textAnchor":74},"186","labels already placed",[44,153],{"x":154,"y":79,"width":155,"height":81,"rx":82,"fill":156,"stroke":157,"style":158},"520","216","#eff3ff","#2563eb","stroke-width:2.5",[68,160,162],{"x":161,"y":89,"style":90,"fill":157,"textAnchor":74},"628","outcome",[44,164],{"x":165,"y":166,"width":167,"height":168,"rx":96,"fill":83,"stroke":169,"style":140},"544","100","168","42","#15803d",[68,171,173],{"x":161,"y":172,"style":118,"fill":66,"textAnchor":74},"126","a candidate fits — drawn",[44,175],{"x":165,"y":95,"width":167,"height":168,"rx":96,"fill":83,"stroke":176,"style":140},"#b91c1c",[68,178,180],{"x":161,"y":179,"style":118,"fill":66,"textAnchor":74},"176","none fits — dropped",[68,182,183],{"x":161,"y":155,"style":118,"fill":84,"textAnchor":74},"priority decides who tries first",[185,186],"line",{"x1":187,"y1":100,"x2":188,"y2":100,"stroke":66,"style":189},"236","266","stroke-width:2;marker-end:url(#labArrow)",[185,191],{"x1":192,"y1":100,"x2":193,"y2":100,"stroke":66,"style":189},"484","514",[68,195,198],{"x":70,"y":196,"style":197,"fill":84,"textAnchor":74},"268","text-anchor:middle;font-size:11px;font-family:sans-serif","The engine never warns you — a missing label looks like a map with fewer labels",[200,201,203],"h2",{"id":202},"prerequisites","Prerequisites",[205,206,207,215,223],"ul",{},[208,209,210,214],"li",{},[211,212,213],"strong",{},"QGIS 3.34 LTR"," (bundled Python 3.12) or newer.",[208,216,217,218,222],{},"A layer with labels already enabled — see ",[21,219,221],{"href":220},"\u002Fpyqgis-cartography-visualization\u002Flabeling-and-annotations\u002Fadd-rule-based-labels-pyqgis\u002F","Add Rule-Based Labels in PyQGIS"," for the labelling basics.",[208,224,225],{},"A crowded extent, which is where any of this becomes visible.",[200,227,229],{"id":228},"set-the-placement-mode","Set the placement mode",[14,231,232],{},"Placement is a property of the label settings, and the sensible value depends entirely on geometry type.",[234,235,240],"pre",{"className":236,"code":237,"language":238,"meta":239,"style":239},"language-python shiki shiki-themes github-dark","from qgis.core import (QgsPalLayerSettings, QgsVectorLayerSimpleLabeling,\n                       QgsTextFormat, Qgis)\n\nsettings = QgsPalLayerSettings()\nsettings.fieldName = \"name\"\nsettings.enabled = True\n\nsettings.placement = Qgis.LabelPlacement.OverPoint      # points\nsettings.quadOffset = Qgis.LabelQuadrantPosition.AboveRight\nsettings.dist = 2.0\n\nlayer.setLabeling(QgsVectorLayerSimpleLabeling(settings))\nlayer.setLabelsEnabled(True)\nlayer.triggerRepaint()\n","python","",[241,242,243,261,267,274,286,298,310,315,330,341,352,357,363,375],"code",{"__ignoreMap":239},[244,245,247,251,255,258],"span",{"class":185,"line":246},1,[244,248,250],{"class":249},"snl16","from",[244,252,254],{"class":253},"s95oV"," qgis.core ",[244,256,257],{"class":249},"import",[244,259,260],{"class":253}," (QgsPalLayerSettings, QgsVectorLayerSimpleLabeling,\n",[244,262,264],{"class":185,"line":263},2,[244,265,266],{"class":253},"                       QgsTextFormat, Qgis)\n",[244,268,270],{"class":185,"line":269},3,[244,271,273],{"emptyLinePlaceholder":272},true,"\n",[244,275,277,280,283],{"class":185,"line":276},4,[244,278,279],{"class":253},"settings ",[244,281,282],{"class":249},"=",[244,284,285],{"class":253}," QgsPalLayerSettings()\n",[244,287,289,292,294],{"class":185,"line":288},5,[244,290,291],{"class":253},"settings.fieldName ",[244,293,282],{"class":249},[244,295,297],{"class":296},"sU2Wk"," \"name\"\n",[244,299,301,304,306],{"class":185,"line":300},6,[244,302,303],{"class":253},"settings.enabled ",[244,305,282],{"class":249},[244,307,309],{"class":308},"sDLfK"," True\n",[244,311,313],{"class":185,"line":312},7,[244,314,273],{"emptyLinePlaceholder":272},[244,316,318,321,323,326],{"class":185,"line":317},8,[244,319,320],{"class":253},"settings.placement ",[244,322,282],{"class":249},[244,324,325],{"class":253}," Qgis.LabelPlacement.OverPoint      ",[244,327,329],{"class":328},"sjoCn","# points\n",[244,331,333,336,338],{"class":185,"line":332},9,[244,334,335],{"class":253},"settings.quadOffset ",[244,337,282],{"class":249},[244,339,340],{"class":253}," Qgis.LabelQuadrantPosition.AboveRight\n",[244,342,344,347,349],{"class":185,"line":343},10,[244,345,346],{"class":253},"settings.dist ",[244,348,282],{"class":249},[244,350,351],{"class":308}," 2.0\n",[244,353,355],{"class":185,"line":354},11,[244,356,273],{"emptyLinePlaceholder":272},[244,358,360],{"class":185,"line":359},12,[244,361,362],{"class":253},"layer.setLabeling(QgsVectorLayerSimpleLabeling(settings))\n",[244,364,366,369,372],{"class":185,"line":365},13,[244,367,368],{"class":253},"layer.setLabelsEnabled(",[244,370,371],{"class":308},"True",[244,373,374],{"class":253},")\n",[244,376,378],{"class":185,"line":377},14,[244,379,380],{"class":253},"layer.triggerRepaint()\n",[14,382,383,386,387,390,391,394,395,398,399,402,403,407,408,411,412,414,415,417,418,421,422,425],{},[211,384,385],{},"Breakdown:"," For points, ",[241,388,389],{},"OverPoint"," with a quadrant offset places the label at a fixed position relative to the symbol, which produces a tidy, predictable map; ",[241,392,393],{},"AroundPoint"," lets the engine try several positions and fit more labels at the cost of consistency. For lines, ",[241,396,397],{},"Line"," follows the geometry — with ",[241,400,401],{},"Curved"," bending the text along it, as covered in ",[21,404,406],{"href":405},"\u002Fpyqgis-cartography-visualization\u002Flabeling-and-annotations\u002Fcurved-labels-along-lines-pyqgis\u002F","Curved Labels Along Lines in PyQGIS"," — and ",[241,409,410],{},"Horizontal"," keeps text upright, which reads better for short line features. For polygons, ",[241,413,393],{}," at the centroid suits small shapes, ",[241,416,410],{}," suits large ones, and ",[241,419,420],{},"PerimeterCurved"," suits long thin ones such as rivers. ",[241,423,424],{},"dist"," is the gap between the feature and its label in the current units.",[200,427,429],{"id":428},"decide-who-wins-a-collision","Decide who wins a collision",[234,431,433],{"className":236,"code":432,"language":238,"meta":239,"style":239},"settings.priority = 8                  # 0 lowest, 10 highest\nsettings.obstacleSettings().setIsObstacle(True)\nsettings.obstacleSettings().setFactor(1.5)\n",[241,434,435,448,457],{"__ignoreMap":239},[244,436,437,440,442,445],{"class":185,"line":246},[244,438,439],{"class":253},"settings.priority ",[244,441,282],{"class":249},[244,443,444],{"class":308}," 8",[244,446,447],{"class":328},"                  # 0 lowest, 10 highest\n",[244,449,450,453,455],{"class":185,"line":263},[244,451,452],{"class":253},"settings.obstacleSettings().setIsObstacle(",[244,454,371],{"class":308},[244,456,374],{"class":253},[244,458,459,462,465],{"class":185,"line":269},[244,460,461],{"class":253},"settings.obstacleSettings().setFactor(",[244,463,464],{"class":308},"1.5",[244,466,374],{"class":253},[14,468,469,471,472,476],{},[211,470,385],{}," Priority decides the order in which labels are placed, and therefore which one survives when two want the same space — a layer of towns at priority 9 keeps its labels while a layer of hamlets at priority 3 loses its overlapping ones, which is the correct cartographic outcome and needs no manual editing. The obstacle settings control whether ",[473,474,475],"em",{},"features"," of this layer block other layers' labels: a building footprint layer set as an obstacle stops street names being written across buildings. The factor weights how strongly the obstacle repels, so a value above one makes the engine work harder to avoid it. Priority is per label setting, so a rule-based labelling can give different priorities to different classes of the same layer.",[200,478,480],{"id":479},"allow-overlap-when-a-missing-label-is-worse","Allow overlap when a missing label is worse",[234,482,484],{"className":236,"code":483,"language":238,"meta":239,"style":239},"settings.obstacleSettings().setIsObstacle(False)\nsettings.displayAll = True             # draw every label, overlapping if necessary\n",[241,485,486,495],{"__ignoreMap":239},[244,487,488,490,493],{"class":185,"line":246},[244,489,452],{"class":253},[244,491,492],{"class":308},"False",[244,494,374],{"class":253},[244,496,497,500,502,505],{"class":185,"line":263},[244,498,499],{"class":253},"settings.displayAll ",[244,501,282],{"class":249},[244,503,504],{"class":308}," True",[244,506,507],{"class":328},"             # draw every label, overlapping if necessary\n",[14,509,510,512,513,516],{},[211,511,385],{}," ",[241,514,515],{},"displayAll"," switches off collision detection for this layer, so every feature gets a label whether or not it collides. That sounds like a mistake and is occasionally exactly right: on a technical plan where every parcel reference must appear, an overlapping label the reader can untangle beats a missing one they cannot. The honest version of this decision is usually to reduce what is labelled instead — filter the features, or label only above a certain scale — because a map with a hundred overlapping labels communicates less than one with twenty placed well. Use it deliberately, and never on a layer where the labels are decorative.",[14,518,519],{},[29,520,523,526,529,532,535,540,544,550,555,559,563,566,570,573,576,579,583,587,591,596,599,602,606,609,612,615,619,622],{"viewBox":521,"role":32,"ariaLabel":522,"xmlns":34},"0 0 760 258","Comparison of three collision strategies, dropping colliding labels, allowing all labels to overlap, and reducing what is labelled by scale or filter",[36,524,525],{},"Three answers to too many labels",[40,527,528],{},"Dropping colliding labels produces a clean map that silently omits features. Allowing all labels to overlap shows everything but becomes unreadable where features are dense. Reducing what is labelled, by filtering features or by scale, keeps the map readable and makes the omission a deliberate choice rather than an accident of placement order.",[44,530],{"x":46,"y":46,"width":47,"height":531,"fill":49},"258",[68,533,534],{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"Only the third one is a decision you made",[44,536],{"x":537,"y":112,"width":538,"height":539,"rx":82,"fill":127,"stroke":128,"style":158},"16","232","188",[68,541,543],{"x":143,"y":542,"style":90,"fill":128,"textAnchor":74},"74","drop collisions — the default",[44,545],{"x":546,"y":547,"width":548,"height":549,"rx":139,"fill":83,"stroke":84,"style":140},"40","90","184","70",[68,551,554],{"x":79,"y":552,"style":553,"fill":66},"112","font-size:10px;font-family:sans-serif","Ashford",[68,556,558],{"x":95,"y":557,"style":553,"fill":66},"134","Deal",[68,560,562],{"x":79,"y":561,"style":553,"fill":84},"152","(three missing)",[68,564,565],{"x":143,"y":548,"style":197,"fill":66,"textAnchor":74},"clean, and incomplete",[68,567,569],{"x":143,"y":568,"style":118,"fill":128,"textAnchor":74},"208","nobody is told what is missing",[44,571],{"x":572,"y":112,"width":538,"height":539,"rx":82,"fill":127,"stroke":176,"style":158},"264",[68,574,575],{"x":70,"y":542,"style":90,"fill":176,"textAnchor":74},"displayAll — overlap",[44,577],{"x":578,"y":547,"width":548,"height":549,"rx":139,"fill":83,"stroke":84,"style":140},"288",[68,580,554],{"x":581,"y":582,"style":553,"fill":66},"300","110",[68,584,558],{"x":585,"y":586,"style":553,"fill":66},"330","118",[68,588,590],{"x":589,"y":88,"style":553,"fill":66},"316","Sandwich",[68,592,595],{"x":593,"y":594,"style":553,"fill":66},"344","140","Hythe",[68,597,598],{"x":70,"y":548,"style":197,"fill":66,"textAnchor":74},"complete, and unreadable",[68,600,601],{"x":70,"y":568,"style":118,"fill":176,"textAnchor":74},"right only when every label must appear",[44,603],{"x":604,"y":112,"width":538,"height":539,"rx":82,"fill":605,"stroke":169,"style":158},"512","#edf8e9",[68,607,608],{"x":161,"y":542,"style":90,"fill":169,"textAnchor":74},"label fewer features",[44,610],{"x":611,"y":547,"width":548,"height":549,"rx":139,"fill":83,"stroke":84,"style":140},"536",[68,613,554],{"x":614,"y":552,"style":553,"fill":66},"552",[68,616,558],{"x":617,"y":618,"style":553,"fill":66},"640","136",[68,620,621],{"x":161,"y":548,"style":197,"fill":66,"textAnchor":74},"readable, and honest",[68,623,624],{"x":161,"y":568,"style":118,"fill":169,"textAnchor":74},"filter or scale rule, chosen by you",[200,626,628],{"id":627},"label-only-what-belongs-at-this-scale","Label only what belongs at this scale",[234,630,632],{"className":236,"code":631,"language":238,"meta":239,"style":239},"settings.scaleVisibility = True\nsettings.minimumScale = 250000        # not labelled when zoomed further out\nsettings.maximumScale = 5000          # not labelled when zoomed further in\n\nsettings.fieldName = \"name\"\nsettings.isExpression = False\nsettings.setDataDefinedProperty(\n    QgsPalLayerSettings.Show,\n    QgsProperty.fromExpression(\"\\\"population\\\" > 10000\"))\n",[241,633,634,643,656,669,673,681,691,696,701],{"__ignoreMap":239},[244,635,636,639,641],{"class":185,"line":246},[244,637,638],{"class":253},"settings.scaleVisibility ",[244,640,282],{"class":249},[244,642,309],{"class":308},[244,644,645,648,650,653],{"class":185,"line":263},[244,646,647],{"class":253},"settings.minimumScale ",[244,649,282],{"class":249},[244,651,652],{"class":308}," 250000",[244,654,655],{"class":328},"        # not labelled when zoomed further out\n",[244,657,658,661,663,666],{"class":185,"line":269},[244,659,660],{"class":253},"settings.maximumScale ",[244,662,282],{"class":249},[244,664,665],{"class":308}," 5000",[244,667,668],{"class":328},"          # not labelled when zoomed further in\n",[244,670,671],{"class":185,"line":276},[244,672,273],{"emptyLinePlaceholder":272},[244,674,675,677,679],{"class":185,"line":288},[244,676,291],{"class":253},[244,678,282],{"class":249},[244,680,297],{"class":296},[244,682,683,686,688],{"class":185,"line":300},[244,684,685],{"class":253},"settings.isExpression ",[244,687,282],{"class":249},[244,689,690],{"class":308}," False\n",[244,692,693],{"class":185,"line":312},[244,694,695],{"class":253},"settings.setDataDefinedProperty(\n",[244,697,698],{"class":185,"line":317},[244,699,700],{"class":253},"    QgsPalLayerSettings.Show,\n",[244,702,703,706,709,712,715,717,720],{"class":185,"line":332},[244,704,705],{"class":253},"    QgsProperty.fromExpression(",[244,707,708],{"class":296},"\"",[244,710,711],{"class":308},"\\\"",[244,713,714],{"class":296},"population",[244,716,711],{"class":308},[244,718,719],{"class":296}," > 10000\"",[244,721,722],{"class":253},"))\n",[14,724,725,727,728,731,732,735],{},[211,726,385],{}," Scale-based label visibility is the cleanest solution to overcrowding: label towns at regional scales and hamlets only when zoomed in, and the map is readable at every scale without any collision drama. Remember that the ",[473,729,730],{},"minimum"," scale is the zoomed-out limit, which reads backwards until you think in scale denominators. The data-defined ",[241,733,734],{},"Show"," property goes further, deciding per feature whether to label at all — an expression here is far better than a filter on the layer, because the unlabelled features still draw. Together these two produce the \"label fewer things\" strategy, which is nearly always the right answer to a crowded map.",[200,737,739],{"id":738},"add-callouts-instead-of-moving-labels-away","Add callouts instead of moving labels away",[14,741,742],{},"When a label must sit away from its feature, a callout line keeps the association clear.",[234,744,746],{"className":236,"code":745,"language":238,"meta":239,"style":239},"from qgis.core import QgsSimpleLineCallout, QgsProperty\n\ncallout = QgsSimpleLineCallout()\ncallout.setEnabled(True)\ncallout.setMinimumLength(2.0)\ncallout.lineSymbol().setWidth(0.3)\n\nsettings.setCallout(callout)\nsettings.placement = Qgis.LabelPlacement.AroundPoint\nsettings.dist = 6.0\n",[241,747,748,759,763,773,782,792,802,806,811,820],{"__ignoreMap":239},[244,749,750,752,754,756],{"class":185,"line":246},[244,751,250],{"class":249},[244,753,254],{"class":253},[244,755,257],{"class":249},[244,757,758],{"class":253}," QgsSimpleLineCallout, QgsProperty\n",[244,760,761],{"class":185,"line":263},[244,762,273],{"emptyLinePlaceholder":272},[244,764,765,768,770],{"class":185,"line":269},[244,766,767],{"class":253},"callout ",[244,769,282],{"class":249},[244,771,772],{"class":253}," QgsSimpleLineCallout()\n",[244,774,775,778,780],{"class":185,"line":276},[244,776,777],{"class":253},"callout.setEnabled(",[244,779,371],{"class":308},[244,781,374],{"class":253},[244,783,784,787,790],{"class":185,"line":288},[244,785,786],{"class":253},"callout.setMinimumLength(",[244,788,789],{"class":308},"2.0",[244,791,374],{"class":253},[244,793,794,797,800],{"class":185,"line":300},[244,795,796],{"class":253},"callout.lineSymbol().setWidth(",[244,798,799],{"class":308},"0.3",[244,801,374],{"class":253},[244,803,804],{"class":185,"line":312},[244,805,273],{"emptyLinePlaceholder":272},[244,807,808],{"class":185,"line":317},[244,809,810],{"class":253},"settings.setCallout(callout)\n",[244,812,813,815,817],{"class":185,"line":332},[244,814,320],{"class":253},[244,816,282],{"class":249},[244,818,819],{"class":253}," Qgis.LabelPlacement.AroundPoint\n",[244,821,822,824,826],{"class":185,"line":343},[244,823,346],{"class":253},[244,825,282],{"class":249},[244,827,828],{"class":308}," 6.0\n",[14,830,831,833,834,837,838,840],{},[211,832,385],{}," A callout draws a thin line from the label to its feature, which lets labels be pushed out of a dense cluster while remaining unambiguous — the technique every published map of a crowded coastline uses. ",[241,835,836],{},"setMinimumLength()"," suppresses the line when the label is already close enough that a line would be visual noise. Increasing ",[241,839,424],{}," alongside gives the engine room to move labels outward, which is what makes the callout useful in the first place. Callouts cost render time and clutter, so they are for the crowded parts of a map rather than a default.",[14,842,843],{},[29,844,847,850,853,856,859,863,868,870,873,876,878,880,883,885,889,892,896,899,902,905,908,911,915,918,922,926,929,932,935,938,941,946],{"viewBox":845,"role":32,"ariaLabel":846,"xmlns":34},"0 0 760 250","Comparison of a cluster of points labelled in place where labels collide against the same cluster with labels pushed outward and connected by callout lines",[36,848,849],{},"Callouts buy room without losing the link",[40,851,852],{},"Five points close together cannot all carry a label in place: the engine drops three of them. Pushing the labels outward and drawing a thin line back to each point fits all five while keeping it obvious which label belongs to which feature.",[44,854],{"x":46,"y":46,"width":47,"height":855,"fill":49},"250",[68,857,858],{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"Five points, five labels — with a little room",[44,860],{"x":103,"y":861,"width":862,"height":81,"rx":82,"fill":127,"stroke":128,"style":158},"52","348",[68,864,867],{"x":865,"y":866,"style":90,"fill":128,"textAnchor":74},"194","78","labelled in place",[93,869],{"cx":95,"cy":594,"r":59,"fill":97},[93,871],{"cx":179,"cy":872,"r":59,"fill":97},"128",[93,874],{"cx":875,"cy":95,"r":59,"fill":97},"198",[93,877],{"cx":137,"cy":137,"r":59,"fill":97},[93,879],{"cx":80,"cy":88,"r":59,"fill":97},[68,881,558],{"x":882,"y":109,"style":553,"fill":66},"222",[68,884,595],{"x":166,"y":143,"style":553,"fill":66},[68,886,888],{"x":865,"y":887,"style":197,"fill":66,"textAnchor":74},"200","three labels dropped, silently",[44,890],{"x":891,"y":861,"width":862,"height":81,"rx":82,"fill":605,"stroke":169,"style":158},"392",[68,893,895],{"x":894,"y":866,"style":90,"fill":169,"textAnchor":74},"566","pushed out with callouts",[93,897],{"cx":898,"cy":594,"r":59,"fill":97},"556",[93,900],{"cx":901,"cy":872,"r":59,"fill":97},"580",[93,903],{"cx":904,"cy":95,"r":59,"fill":97},"600",[93,906],{"cx":907,"cy":137,"r":59,"fill":97},"568",[93,909],{"cx":910,"cy":88,"r":59,"fill":97},"614",[185,912],{"x1":898,"y1":594,"x2":913,"y2":552,"stroke":84,"style":914},"470","stroke-width:1",[68,916,595],{"x":917,"y":582,"style":553,"fill":66},"424",[185,919],{"x1":901,"y1":872,"x2":920,"y2":921,"stroke":84,"style":914},"500","92",[68,923,558],{"x":924,"y":925,"style":553,"fill":66},"452","88",[185,927],{"x1":904,"y1":95,"x2":928,"y2":179,"stroke":84,"style":914},"682",[68,930,590],{"x":931,"y":81,"style":553,"fill":66},"688",[185,933],{"x1":907,"y1":137,"x2":934,"y2":539,"stroke":84,"style":914},"486",[68,936,554],{"x":917,"y":937,"style":553,"fill":66},"192",[185,939],{"x1":910,"y1":88,"x2":940,"y2":136,"stroke":84,"style":914},"678",[68,942,945],{"x":943,"y":944,"style":553,"fill":66},"684","102","Dover",[68,947,948],{"x":894,"y":155,"style":90,"fill":169,"textAnchor":74},"all five, and no ambiguity",[200,950,952],{"id":951},"find-out-why-a-label-is-missing","Find out why a label is missing",[14,954,955],{},"The engine gives no warning, so diagnosis is a short checklist worth running in order.",[14,957,958,961,962,965],{},[211,959,960],{},"Is the feature labelled at all?"," Check the field for ",[241,963,964],{},"NULL"," or an empty string — a feature with no value is not a missing label, it is a feature with nothing to say.",[14,967,968,974],{},[211,969,970,971,973],{},"Is it excluded by scale or a data-defined ",[241,972,734],{},"?"," Both fail silently by design. Print the settings, or temporarily disable them.",[14,976,977,980],{},[211,978,979],{},"Is another layer blocking it?"," Turn off obstacles on the other layers one at a time. Buildings and polygon fills are the usual culprits.",[14,982,983,986],{},[211,984,985],{},"Is it losing a collision?"," Raise the priority to 10 temporarily. If the label appears, it was being outranked, and the real fix is a considered priority ordering across layers rather than everything at 10.",[14,988,989,992,993,996],{},[211,990,991],{},"Is it simply too big to fit?"," A long name at a large font in a small polygon has no valid position anywhere. Shorten it with an expression, wrap it with ",[241,994,995],{},"settings.autoWrapLength",", or let it sit outside the polygon with a callout.",[14,998,999],{},"Working through those five in order settles nearly every case, and the order matters because the cheap checks are first.",[200,1001,1003],{"id":1002},"qgis-version-compatibility","QGIS version compatibility",[1005,1006,1007,1023],"table",{},[1008,1009,1010],"thead",{},[1011,1012,1013,1017,1020],"tr",{},[1014,1015,1016],"th",{},"QGIS version",[1014,1018,1019],{},"Python",[1014,1021,1022],{},"Notes",[1024,1025,1026,1046,1056,1071],"tbody",{},[1011,1027,1028,1032,1035],{},[1029,1030,1031],"td",{},"3.22 LTR",[1029,1033,1034],{},"3.9",[1029,1036,1037,1038,1041,1042,1045],{},"Placement and obstacle settings present; enumerations live on ",[241,1039,1040],{},"QgsPalLayerSettings"," rather than ",[241,1043,1044],{},"Qgis",".",[1011,1047,1048,1051,1053],{},[1029,1049,1050],{},"3.28 LTR",[1029,1052,1034],{},[1029,1054,1055],{},"Both enumeration spellings available.",[1011,1057,1058,1061,1064],{},[1029,1059,1060],{},"3.34 LTR",[1029,1062,1063],{},"3.12",[1029,1065,1066,1067,1070],{},"Baseline for this page; scoped ",[241,1068,1069],{},"Qgis.LabelPlacement"," names preferred.",[1011,1072,1073,1076,1078],{},[1029,1074,1075],{},"3.40 \u002F 3.44",[1029,1077,1063],{},[1029,1079,1080],{},"Identical behaviour; legacy enumeration aliases are being removed, so prefer the scoped names.",[14,1082,1083,1084,1087,1088,1091,1092,1095,1096,1100],{},"If a placement constant raises an ",[241,1085,1086],{},"AttributeError",", check which spelling your release uses with ",[241,1089,1090],{},"dir(Qgis)"," and ",[241,1093,1094],{},"dir(QgsPalLayerSettings)"," — the introspection approach in ",[21,1097,1099],{"href":1098},"\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-api-architecture\u002Fexplore-pyqgis-api-with-dir-and-help\u002F","Explore the PyQGIS API with dir() and help()"," settles it immediately.",[200,1102,1104],{"id":1103},"troubleshooting","Troubleshooting",[205,1106,1107,1123,1129,1138,1144,1150],{},[208,1108,1109,512,1112,1115,1116,1119,1120,1122],{},[211,1110,1111],{},"No labels at all.",[241,1113,1114],{},"setLabelsEnabled(True)"," was not called, or ",[241,1117,1118],{},"settings.enabled"," is ",[241,1121,492],{},". Both are required.",[208,1124,1125,1128],{},[211,1126,1127],{},"Some labels missing at every zoom."," Collisions. Raise priority, reduce obstacles, or label fewer features.",[208,1130,1131,1134,1135,1137],{},[211,1132,1133],{},"Labels disappear when zooming in."," A maximum scale is set, or a data-defined ",[241,1136,734],{}," expression is excluding them.",[208,1139,1140,1143],{},[211,1141,1142],{},"Labels are drawn over the wrong features."," Obstacle settings on the other layers. Mark solid layers as obstacles.",[208,1145,1146,1149],{},[211,1147,1148],{},"Curved labels on lines look broken."," The line is too short or too sharply bent for the text. Fall back to horizontal placement for short features.",[208,1151,1152,512,1155,1157],{},[211,1153,1154],{},"Everything overlaps.",[241,1156,515],{}," is on. It is an escape hatch, not a default.",[200,1159,1161],{"id":1160},"conclusion","Conclusion",[14,1163,1164,1165,1167],{},"Choose a placement mode that suits the geometry, set priorities so the important layer wins collisions, and mark solid layers as obstacles so labels avoid them. Prefer labelling fewer features — by scale rule or a data-defined ",[241,1166,734],{}," expression — over allowing everything to overlap, use callouts where labels must sit away from their feature, and when a label is missing work through the five checks in order rather than changing settings at random.",[200,1169,1171],{"id":1170},"frequently-asked-questions","Frequently Asked Questions",[14,1173,1174,1177],{},[211,1175,1176],{},"Why does QGIS drop labels instead of shrinking them?","\nBecause a variable font size makes a map harder to read, not easier. Where shrinking is acceptable, a data-defined size expression can do it explicitly.",[14,1179,1180,1183],{},[211,1181,1182],{},"Can I place a label at an exact position?","\nYes — data-defined position properties take X and Y from fields, which is how manually placed labels are stored. It is the right tool for a small number of stubborn labels.",[14,1185,1186,1189],{},[211,1187,1188],{},"How do I stop labels being cut off at the map frame?","\nEnable label margins on the layout map item, so labels near the edge are drawn inside the frame rather than clipped.",[14,1191,1192,1195],{},[211,1193,1194],{},"Do priorities work across layers?","\nYes — the whole map is labelled by one engine pass, so priority ranks labels from every layer against each other.",[14,1197,1198,1201,1202,1204],{},[211,1199,1200],{},"Is there a way to see which labels were dropped?","\nNot directly. Compare the labelled count against the feature count by rendering with ",[241,1203,515],{}," temporarily and looking at the difference.",[14,1206,1207,1210,1211,1045],{},[211,1208,1209],{},"Should labels be a layer property or a layout decision?","\nThe layer holds the labelling; a layout map can override it through a map theme, which is how one project produces a busy screen map and a clean printed one — see ",[21,1212,1214],{"href":1213},"\u002Fspatial-data-processing-automation\u002Fautomated-map-layout-generation\u002Fadd-map-item-and-set-extent-pyqgis\u002F","Add a Map Item and Set Its Extent in PyQGIS",[200,1216,1218],{"id":1217},"related","Related",[205,1220,1221,1226,1230,1234,1240],{},[208,1222,1223,1225],{},[21,1224,24],{"href":23}," — the guide this recipe belongs to",[208,1227,1228],{},[21,1229,221],{"href":220},[208,1231,1232],{},[21,1233,406],{"href":405},[208,1235,1236],{},[21,1237,1239],{"href":1238},"\u002Fpyqgis-cartography-visualization\u002Fgraduated-categorized-renderers\u002Fcreate-categorized-renderer-pyqgis\u002F","Create a Categorized Renderer in PyQGIS",[208,1241,1242],{},[21,1243,1245],{"href":1244},"\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-expressions\u002Fdata-defined-symbol-size-expression-pyqgis\u002F","Data-Defined Symbol Size Expression in PyQGIS",[1247,1248,1249],"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 pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}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);}",{"title":239,"searchDepth":263,"depth":263,"links":1251},[1252,1253,1254,1255,1256,1257,1258,1259,1260,1261,1262,1263],{"id":202,"depth":263,"text":203},{"id":228,"depth":263,"text":229},{"id":428,"depth":263,"text":429},{"id":479,"depth":263,"text":480},{"id":627,"depth":263,"text":628},{"id":738,"depth":263,"text":739},{"id":951,"depth":263,"text":952},{"id":1002,"depth":263,"text":1003},{"id":1103,"depth":263,"text":1104},{"id":1160,"depth":263,"text":1161},{"id":1170,"depth":263,"text":1171},{"id":1217,"depth":263,"text":1218},"Decide which labels win and where they sit — placement modes per geometry type, priority and obstacle settings, allowing overlap, callout lines, and diagnosing labels that never appear.","md",{"slug":1267,"type":1268,"breadcrumb":1269,"datePublished":1270,"dateModified":1270},"control-label-placement-and-collisions-pyqgis","article","Label Placement","2026-08-15","\u002Fpyqgis-cartography-visualization\u002Flabeling-and-annotations\u002Fcontrol-label-placement-and-collisions-pyqgis",{"title":5,"description":1264},"pyqgis-cartography-visualization\u002Flabeling-and-annotations\u002Fcontrol-label-placement-and-collisions-pyqgis\u002Findex","X5WdKwQvUrVd43AdssOB6S5l449E1adHdTQS6A0mSnU",1786789584611]