[{"data":1,"prerenderedAt":2186},["ShallowReactive",2],{"doc:\u002Fqgis-plugin-development\u002Fcustom-map-tools-and-canvas-interaction\u002Fbuild-measure-tool-with-qgsdistancearea-pyqgis":3},{"id":4,"title":5,"body":6,"description":2175,"extension":2176,"meta":2177,"navigation":225,"path":2182,"seo":2183,"stem":2184,"__hash__":2185},"docs\u002Fqgis-plugin-development\u002Fcustom-map-tools-and-canvas-interaction\u002Fbuild-measure-tool-with-qgsdistancearea-pyqgis\u002Findex.md","Build a Measure Tool with QgsDistanceArea in PyQGIS",{"type":7,"value":8,"toc":2161},"minimark",[9,13,17,31,164,169,184,188,193,345,360,364,367,422,874,887,891,894,1255,1279,1283,1286,1659,1679,1683,1686,1730,1748,1752,1755,1821,1978,1992,1996,2027,2031,2062,2066,2078,2082,2090,2096,2102,2108,2124,2128,2157],[10,11,5],"h1",{"id":12},"build-a-measure-tool-with-qgsdistancearea-in-pyqgis",[14,15,16],"p",{},"QGIS has a measure tool, but plugins often need their own: measuring along a route and writing the length into a form, checking a setback distance with a pass\u002Ffail answer, measuring the area of a proposed plot and comparing it with a minimum. Building one is a good exercise in the map tool API — clicks, mouse moves, rubber bands — and in getting measurement right, because a measure tool that uses planar distances in a geographic or Web Mercator project gives answers that are wrong by tens of percent.",[14,18,19,20,25,26,30],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fqgis-plugin-development\u002Fcustom-map-tools-and-canvas-interaction\u002F","Custom Map Tools & Canvas Interaction",". It builds a map tool that collects vertices, previews them with a rubber band, measures ellipsoidally with ",[27,28,29],"code",{},"QgsDistanceArea"," in any CRS, updates a live readout, converts units, snaps to features and reports results.",[14,32,33],{},[34,35,40,44,48,55,72,81,90,96,102,107,111,114,121,125,131,135,139,143,147,153,158,161],"svg",{"viewBox":36,"role":37,"ariaLabel":38,"xmlns":39},"0 0 760 216","img","A measure tool where clicks add vertices, mouse moves preview the next segment, and every change recomputes an ellipsoidal measurement shown live","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[41,42,43],"title",{},"A measure tool's event loop",[45,46,47],"desc",{},"Left clicks add vertices to a list and a rubber band; mouse moves update a temporary last segment so the user sees the line before clicking; each change recomputes length or area with QgsDistanceArea on the ellipsoid; the result is shown live. A right click finishes, reports the final value and resets for the next measurement.",[49,50],"rect",{"x":51,"y":51,"width":52,"height":53,"fill":54},"0","760","216","#f6f3ea",[56,57,58],"defs",{},[59,60,67],"marker",{"id":61,"viewBox":62,"refX":63,"refY":64,"markerWidth":65,"markerHeight":65,"orient":66},"msFlowArrow","0 0 10 10","8","5","7","auto-start-reverse",[68,69],"path",{"d":70,"fill":71},"M0 0 L10 5 L0 10 z","#2f3b35",[73,74,80],"text",{"x":75,"y":76,"style":77,"fill":78,"textAnchor":79},"380","28","text-anchor:middle;font-size:14px;font-family:sans-serif;font-weight:bold","#17211d","middle","Click, move, measure, finish",[49,82],{"x":83,"y":84,"width":85,"height":86,"rx":63,"fill":87,"stroke":88,"style":89},"24","70","160","130","#eef7f4","#0f766e","stroke-width:2",[73,91,95],{"x":92,"y":93,"style":94,"fill":88,"textAnchor":79},"104","126.78","text-anchor:middle;font-size:11.5px;font-family:sans-serif;font-weight:bold","left click",[73,97,101],{"x":92,"y":98,"style":99,"fill":100,"textAnchor":79},"150.78","text-anchor:middle;font-size:10.5px;font-family:sans-serif","#59645f","add vertex",[49,103],{"x":104,"y":84,"width":85,"height":86,"rx":63,"fill":105,"stroke":106,"style":89},"204","#eff3ff","#2563eb",[73,108,110],{"x":109,"y":93,"style":94,"fill":106,"textAnchor":79},"284","mouse move",[73,112,113],{"x":109,"y":98,"style":99,"fill":100,"textAnchor":79},"preview segment",[115,116],"line",{"x1":117,"y1":118,"x2":119,"y2":118,"stroke":71,"style":120},"184","135","200","stroke-width:1.8;marker-end:url(#msFlowArrow)",[115,122],{"x1":123,"y1":118,"x2":124,"y2":118,"stroke":71,"style":120},"364","384",[49,126],{"x":127,"y":84,"width":128,"height":86,"rx":63,"fill":129,"stroke":130,"style":89},"388","180","#fdf2e2","#b45309",[73,132,29],{"x":133,"y":134,"style":94,"fill":130,"textAnchor":79},"478","114.78",[73,136,138],{"x":133,"y":137,"style":99,"fill":100,"textAnchor":79},"138.78","ellipsoidal",[73,140,142],{"x":133,"y":141,"style":99,"fill":100,"textAnchor":79},"162.78","length \u002F area",[115,144],{"x1":145,"y1":118,"x2":146,"y2":118,"stroke":71,"style":120},"568","588",[49,148],{"x":149,"y":84,"width":150,"height":86,"rx":63,"fill":151,"stroke":152,"style":89},"592","144","#e8efe6","#15803d",[73,154,157],{"x":155,"y":134,"style":94,"fill":156,"textAnchor":79},"664","#166534","right click",[73,159,160],{"x":155,"y":137,"style":99,"fill":100,"textAnchor":79},"report",[73,162,163],{"x":155,"y":141,"style":99,"fill":100,"textAnchor":79},"reset",[165,166,168],"h2",{"id":167},"prerequisites","Prerequisites",[170,171,172,176],"ul",{},[173,174,175],"li",{},"QGIS 3.34 LTR or newer, or the QGIS 4 series, inside QGIS Desktop.",[173,177,178,179,183],{},"Familiarity with custom map tools from ",[21,180,182],{"href":181},"\u002Fqgis-plugin-development\u002Fcustom-map-tools-and-canvas-interaction\u002Fcreate-custom-map-tool-pyqgis\u002F","creating a custom map tool",".",[165,185,187],{"id":186},"set-up-ellipsoidal-measurement","Set up ellipsoidal measurement",[14,189,190,192],{},[27,191,29],{}," measures lengths and areas on an ellipsoid when given the source CRS and an ellipsoid, regardless of the map's projection. Configuring it from the project gives the same results as QGIS's own tools.",[194,195,200],"pre",{"className":196,"code":197,"language":198,"meta":199,"style":199},"language-python shiki shiki-themes github-dark","from qgis.core import QgsDistanceArea, QgsProject, QgsUnitTypes\n\ndef make_measurer(canvas):\n    da = QgsDistanceArea()\n    project = QgsProject.instance()\n    da.setSourceCrs(canvas.mapSettings().destinationCrs(), project.transformContext())\n    da.setEllipsoid(project.ellipsoid() or \"EPSG:7030\")\n    return da\n\nfrom qgis.utils import iface\nda = make_measurer(iface.mapCanvas())\nprint(\"ellipsoidal:\", da.willUseEllipsoid(), \"| ellipsoid:\", da.ellipsoid())\n","python","",[27,201,202,220,227,240,252,263,269,285,294,299,312,323],{"__ignoreMap":199},[203,204,206,210,214,217],"span",{"class":115,"line":205},1,[203,207,209],{"class":208},"snl16","from",[203,211,213],{"class":212},"s95oV"," qgis.core ",[203,215,216],{"class":208},"import",[203,218,219],{"class":212}," QgsDistanceArea, QgsProject, QgsUnitTypes\n",[203,221,223],{"class":115,"line":222},2,[203,224,226],{"emptyLinePlaceholder":225},true,"\n",[203,228,230,233,237],{"class":115,"line":229},3,[203,231,232],{"class":208},"def",[203,234,236],{"class":235},"svObZ"," make_measurer",[203,238,239],{"class":212},"(canvas):\n",[203,241,243,246,249],{"class":115,"line":242},4,[203,244,245],{"class":212},"    da ",[203,247,248],{"class":208},"=",[203,250,251],{"class":212}," QgsDistanceArea()\n",[203,253,255,258,260],{"class":115,"line":254},5,[203,256,257],{"class":212},"    project ",[203,259,248],{"class":208},[203,261,262],{"class":212}," QgsProject.instance()\n",[203,264,266],{"class":115,"line":265},6,[203,267,268],{"class":212},"    da.setSourceCrs(canvas.mapSettings().destinationCrs(), project.transformContext())\n",[203,270,272,275,278,282],{"class":115,"line":271},7,[203,273,274],{"class":212},"    da.setEllipsoid(project.ellipsoid() ",[203,276,277],{"class":208},"or",[203,279,281],{"class":280},"sU2Wk"," \"EPSG:7030\"",[203,283,284],{"class":212},")\n",[203,286,288,291],{"class":115,"line":287},8,[203,289,290],{"class":208},"    return",[203,292,293],{"class":212}," da\n",[203,295,297],{"class":115,"line":296},9,[203,298,226],{"emptyLinePlaceholder":225},[203,300,302,304,307,309],{"class":115,"line":301},10,[203,303,209],{"class":208},[203,305,306],{"class":212}," qgis.utils ",[203,308,216],{"class":208},[203,310,311],{"class":212}," iface\n",[203,313,315,318,320],{"class":115,"line":314},11,[203,316,317],{"class":212},"da ",[203,319,248],{"class":208},[203,321,322],{"class":212}," make_measurer(iface.mapCanvas())\n",[203,324,326,330,333,336,339,342],{"class":115,"line":325},12,[203,327,329],{"class":328},"sDLfK","print",[203,331,332],{"class":212},"(",[203,334,335],{"class":280},"\"ellipsoidal:\"",[203,337,338],{"class":212},", da.willUseEllipsoid(), ",[203,340,341],{"class":280},"\"| ellipsoid:\"",[203,343,344],{"class":212},", da.ellipsoid())\n",[14,346,347,351,352,355,356,359],{},[348,349,350],"strong",{},"Breakdown:"," The source CRS is the canvas CRS, because clicked points arrive in canvas coordinates. The project ellipsoid — set in Project Properties — is what QGIS's measure tool and ",[27,353,354],{},"$area"," use; falling back to WGS 84 (EPSG:7030) covers projects with none set. ",[27,357,358],{},"willUseEllipsoid()"," confirms ellipsoidal measurement is active. In Web Mercator, planar distances are inflated by the scale factor — about 1.6 times at 50° latitude — which is exactly the error ellipsoidal measurement avoids.",[165,361,363],{"id":362},"collect-vertices-with-a-rubber-band-preview","Collect vertices with a rubber band preview",[14,365,366],{},"The map tool records clicked points and draws them with a rubber band; a second, temporary point follows the mouse so the user sees the next segment before clicking.",[14,368,369],{},[34,370,373,376,379,384,388,394,400,404,408,410,413,417],{"viewBox":371,"role":37,"ariaLabel":372,"xmlns":39},"43 0 564 240","A rubber band with fixed clicked vertices and a final point following the mouse to preview the next segment",[41,374,375],{},"Fixed vertices and a moving tip",[45,377,378],{},"The rubber band holds the clicked vertices plus one extra point that follows the mouse. On each mouse move only that last point is moved, so the preview segment stretches to the cursor. On click the moving point becomes fixed and a new moving point is added. Length is measured over all points including the moving one for the live readout.",[49,380],{"x":381,"y":51,"width":382,"height":383,"fill":54},"43","564","240",[73,385,387],{"x":386,"y":76,"style":77,"fill":78,"textAnchor":79},"325","Fixed points, plus one that follows the cursor",[389,390],"polyline",{"points":391,"fill":392,"stroke":106,"style":393},"80,180 220,90 380,150","none","stroke-width:3",[115,395],{"x1":75,"y1":396,"x2":397,"y2":398,"stroke":106,"style":399},"150","560","80","stroke-width:2;stroke-dasharray:6 4",[401,402],"circle",{"cx":398,"cy":128,"r":64,"fill":106,"stroke":78,"style":403},"stroke-width:1",[401,405],{"cx":406,"cy":407,"r":64,"fill":106,"stroke":78,"style":403},"220","90",[401,409],{"cx":75,"cy":396,"r":64,"fill":106,"stroke":78,"style":403},[401,411],{"cx":397,"cy":398,"r":412,"fill":54,"stroke":106,"style":89},"4",[73,414,416],{"x":397,"y":415,"style":99,"fill":106,"textAnchor":79},"64","cursor",[73,418,421],{"x":419,"y":420,"style":99,"fill":100,"textAnchor":79},"300","214","clicked vertices (solid) + preview to cursor (dashed)",[194,423,425],{"className":196,"code":424,"language":198,"meta":199,"style":199},"from qgis.gui import QgsMapTool, QgsRubberBand\nfrom qgis.core import QgsWkbTypes, QgsPointXY\nfrom qgis.PyQt.QtGui import QColor\nfrom qgis.PyQt.QtCore import Qt\n\nclass MeasureTool(QgsMapTool):\n    def __init__(self, canvas, area=False, on_result=None):\n        super().__init__(canvas)\n        self.canvas = canvas\n        self.area = area\n        self.on_result = on_result\n        gtype = QgsWkbTypes.PolygonGeometry if area else QgsWkbTypes.LineGeometry\n        self.band = QgsRubberBand(canvas, gtype)\n        self.band.setColor(QColor(37, 99, 235, 90 if area else 255))\n        self.band.setStrokeColor(QColor(\"#2563eb\"))\n        self.band.setWidth(2)\n        self.points = []\n        self.setCursor(Qt.CrossCursor)\n\n    def canvasReleaseEvent(self, event):\n        point = self.toMapCoordinates(event.pos())\n        if event.button() == Qt.LeftButton:\n            self.points.append(point)\n            self.band.addPoint(point, True)\n            self.band.addPoint(point, True)      # moving tip\n        elif event.button() == Qt.RightButton and self.points:\n            self.finish()\n\n    def canvasMoveEvent(self, event):\n        if not self.points:\n            return\n        self.band.movePoint(self.toMapCoordinates(event.pos()))\n        self.update_readout(self.points + [self.toMapCoordinates(event.pos())])\n",[27,426,427,439,450,462,474,478,494,520,534,547,559,571,593,606,645,658,671,684,692,697,708,722,737,746,759,775,796,804,809,819,831,837,851],{"__ignoreMap":199},[203,428,429,431,434,436],{"class":115,"line":205},[203,430,209],{"class":208},[203,432,433],{"class":212}," qgis.gui ",[203,435,216],{"class":208},[203,437,438],{"class":212}," QgsMapTool, QgsRubberBand\n",[203,440,441,443,445,447],{"class":115,"line":222},[203,442,209],{"class":208},[203,444,213],{"class":212},[203,446,216],{"class":208},[203,448,449],{"class":212}," QgsWkbTypes, QgsPointXY\n",[203,451,452,454,457,459],{"class":115,"line":229},[203,453,209],{"class":208},[203,455,456],{"class":212}," qgis.PyQt.QtGui ",[203,458,216],{"class":208},[203,460,461],{"class":212}," QColor\n",[203,463,464,466,469,471],{"class":115,"line":242},[203,465,209],{"class":208},[203,467,468],{"class":212}," qgis.PyQt.QtCore ",[203,470,216],{"class":208},[203,472,473],{"class":212}," Qt\n",[203,475,476],{"class":115,"line":254},[203,477,226],{"emptyLinePlaceholder":225},[203,479,480,483,486,488,491],{"class":115,"line":265},[203,481,482],{"class":208},"class",[203,484,485],{"class":235}," MeasureTool",[203,487,332],{"class":212},[203,489,490],{"class":235},"QgsMapTool",[203,492,493],{"class":212},"):\n",[203,495,496,499,502,505,507,510,513,515,518],{"class":115,"line":271},[203,497,498],{"class":208},"    def",[203,500,501],{"class":328}," __init__",[203,503,504],{"class":212},"(self, canvas, area",[203,506,248],{"class":208},[203,508,509],{"class":328},"False",[203,511,512],{"class":212},", on_result",[203,514,248],{"class":208},[203,516,517],{"class":328},"None",[203,519,493],{"class":212},[203,521,522,525,528,531],{"class":115,"line":287},[203,523,524],{"class":328},"        super",[203,526,527],{"class":212},"().",[203,529,530],{"class":328},"__init__",[203,532,533],{"class":212},"(canvas)\n",[203,535,536,539,542,544],{"class":115,"line":296},[203,537,538],{"class":328},"        self",[203,540,541],{"class":212},".canvas ",[203,543,248],{"class":208},[203,545,546],{"class":212}," canvas\n",[203,548,549,551,554,556],{"class":115,"line":301},[203,550,538],{"class":328},[203,552,553],{"class":212},".area ",[203,555,248],{"class":208},[203,557,558],{"class":212}," area\n",[203,560,561,563,566,568],{"class":115,"line":314},[203,562,538],{"class":328},[203,564,565],{"class":212},".on_result ",[203,567,248],{"class":208},[203,569,570],{"class":212}," on_result\n",[203,572,573,576,578,581,584,587,590],{"class":115,"line":325},[203,574,575],{"class":212},"        gtype ",[203,577,248],{"class":208},[203,579,580],{"class":212}," QgsWkbTypes.PolygonGeometry ",[203,582,583],{"class":208},"if",[203,585,586],{"class":212}," area ",[203,588,589],{"class":208},"else",[203,591,592],{"class":212}," QgsWkbTypes.LineGeometry\n",[203,594,596,598,601,603],{"class":115,"line":595},13,[203,597,538],{"class":328},[203,599,600],{"class":212},".band ",[203,602,248],{"class":208},[203,604,605],{"class":212}," QgsRubberBand(canvas, gtype)\n",[203,607,609,611,614,617,620,623,625,628,630,632,635,637,639,642],{"class":115,"line":608},14,[203,610,538],{"class":328},[203,612,613],{"class":212},".band.setColor(QColor(",[203,615,616],{"class":328},"37",[203,618,619],{"class":212},", ",[203,621,622],{"class":328},"99",[203,624,619],{"class":212},[203,626,627],{"class":328},"235",[203,629,619],{"class":212},[203,631,407],{"class":328},[203,633,634],{"class":208}," if",[203,636,586],{"class":212},[203,638,589],{"class":208},[203,640,641],{"class":328}," 255",[203,643,644],{"class":212},"))\n",[203,646,648,650,653,656],{"class":115,"line":647},15,[203,649,538],{"class":328},[203,651,652],{"class":212},".band.setStrokeColor(QColor(",[203,654,655],{"class":280},"\"#2563eb\"",[203,657,644],{"class":212},[203,659,661,663,666,669],{"class":115,"line":660},16,[203,662,538],{"class":328},[203,664,665],{"class":212},".band.setWidth(",[203,667,668],{"class":328},"2",[203,670,284],{"class":212},[203,672,674,676,679,681],{"class":115,"line":673},17,[203,675,538],{"class":328},[203,677,678],{"class":212},".points ",[203,680,248],{"class":208},[203,682,683],{"class":212}," []\n",[203,685,687,689],{"class":115,"line":686},18,[203,688,538],{"class":328},[203,690,691],{"class":212},".setCursor(Qt.CrossCursor)\n",[203,693,695],{"class":115,"line":694},19,[203,696,226],{"emptyLinePlaceholder":225},[203,698,700,702,705],{"class":115,"line":699},20,[203,701,498],{"class":208},[203,703,704],{"class":235}," canvasReleaseEvent",[203,706,707],{"class":212},"(self, event):\n",[203,709,711,714,716,719],{"class":115,"line":710},21,[203,712,713],{"class":212},"        point ",[203,715,248],{"class":208},[203,717,718],{"class":328}," self",[203,720,721],{"class":212},".toMapCoordinates(event.pos())\n",[203,723,725,728,731,734],{"class":115,"line":724},22,[203,726,727],{"class":208},"        if",[203,729,730],{"class":212}," event.button() ",[203,732,733],{"class":208},"==",[203,735,736],{"class":212}," Qt.LeftButton:\n",[203,738,740,743],{"class":115,"line":739},23,[203,741,742],{"class":328},"            self",[203,744,745],{"class":212},".points.append(point)\n",[203,747,749,751,754,757],{"class":115,"line":748},24,[203,750,742],{"class":328},[203,752,753],{"class":212},".band.addPoint(point, ",[203,755,756],{"class":328},"True",[203,758,284],{"class":212},[203,760,762,764,766,768,771],{"class":115,"line":761},25,[203,763,742],{"class":328},[203,765,753],{"class":212},[203,767,756],{"class":328},[203,769,770],{"class":212},")      ",[203,772,774],{"class":773},"sjoCn","# moving tip\n",[203,776,778,781,783,785,788,791,793],{"class":115,"line":777},26,[203,779,780],{"class":208},"        elif",[203,782,730],{"class":212},[203,784,733],{"class":208},[203,786,787],{"class":212}," Qt.RightButton ",[203,789,790],{"class":208},"and",[203,792,718],{"class":328},[203,794,795],{"class":212},".points:\n",[203,797,799,801],{"class":115,"line":798},27,[203,800,742],{"class":328},[203,802,803],{"class":212},".finish()\n",[203,805,807],{"class":115,"line":806},28,[203,808,226],{"emptyLinePlaceholder":225},[203,810,812,814,817],{"class":115,"line":811},29,[203,813,498],{"class":208},[203,815,816],{"class":235}," canvasMoveEvent",[203,818,707],{"class":212},[203,820,822,824,827,829],{"class":115,"line":821},30,[203,823,727],{"class":208},[203,825,826],{"class":208}," not",[203,828,718],{"class":328},[203,830,795],{"class":212},[203,832,834],{"class":115,"line":833},31,[203,835,836],{"class":208},"            return\n",[203,838,840,842,845,848],{"class":115,"line":839},32,[203,841,538],{"class":328},[203,843,844],{"class":212},".band.movePoint(",[203,846,847],{"class":328},"self",[203,849,850],{"class":212},".toMapCoordinates(event.pos()))\n",[203,852,854,856,859,861,863,866,869,871],{"class":115,"line":853},33,[203,855,538],{"class":328},[203,857,858],{"class":212},".update_readout(",[203,860,847],{"class":328},[203,862,678],{"class":212},[203,864,865],{"class":208},"+",[203,867,868],{"class":212}," [",[203,870,847],{"class":328},[203,872,873],{"class":212},".toMapCoordinates(event.pos())])\n",[14,875,876,878,879,882,883,886],{},[348,877,350],{}," A line rubber band for distances and a polygon rubber band for areas give the right preview in each mode. Each click adds the point twice: once as a fixed vertex and once as the moving tip that ",[27,880,881],{},"movePoint"," drags on mouse moves, so the preview segment stretches to the cursor. Right click finishes. ",[27,884,885],{},"toMapCoordinates"," converts the mouse position to canvas CRS coordinates — the CRS the measurer was set up with. The readout is computed from the fixed points plus the cursor position.",[165,888,890],{"id":889},"measure-and-format-results","Measure and format results",[14,892,893],{},"The measurement itself is one call; presenting it well — sensible units and precision — is what makes the tool pleasant.",[194,895,897],{"className":196,"code":896,"language":198,"meta":199,"style":199},"    def update_readout(self, pts, final=False):\n        da = make_measurer(self.canvas)\n        if self.area and len(pts) >= 3:\n            from qgis.core import QgsGeometry\n            value = da.measureArea(QgsGeometry.fromPolygonXY([pts + [pts[0]]]))\n            text = da.formatArea(value, 2, QgsUnitTypes.AreaSquareMeters, True)\n        elif not self.area and len(pts) >= 2:\n            value = da.measureLine(pts)\n            text = da.formatDistance(value, 2, QgsUnitTypes.DistanceMeters, False)\n        else:\n            return\n        iface.statusBarIface().showMessage((\"Final: \" if final else \"\") + text)\n        return value\n\n    def finish(self):\n        value = self.update_readout(self.points, final=True)\n        if self.on_result and value is not None:\n            self.on_result(value, list(self.points))\n        self.reset()\n\n    def reset(self):\n        self.points = []\n        self.band.reset(QgsWkbTypes.PolygonGeometry if self.area else QgsWkbTypes.LineGeometry)\n\n    def deactivate(self):\n        self.reset()\n        super().deactivate()\n",[27,898,899,915,930,955,967,987,1006,1029,1038,1056,1063,1067,1093,1101,1105,1115,1141,1164,1181,1188,1192,1201,1211,1229,1233,1242,1248],{"__ignoreMap":199},[203,900,901,903,906,909,911,913],{"class":115,"line":205},[203,902,498],{"class":208},[203,904,905],{"class":235}," update_readout",[203,907,908],{"class":212},"(self, pts, final",[203,910,248],{"class":208},[203,912,509],{"class":328},[203,914,493],{"class":212},[203,916,917,920,922,925,927],{"class":115,"line":222},[203,918,919],{"class":212},"        da ",[203,921,248],{"class":208},[203,923,924],{"class":212}," make_measurer(",[203,926,847],{"class":328},[203,928,929],{"class":212},".canvas)\n",[203,931,932,934,936,938,940,943,946,949,952],{"class":115,"line":229},[203,933,727],{"class":208},[203,935,718],{"class":328},[203,937,553],{"class":212},[203,939,790],{"class":208},[203,941,942],{"class":328}," len",[203,944,945],{"class":212},"(pts) ",[203,947,948],{"class":208},">=",[203,950,951],{"class":328}," 3",[203,953,954],{"class":212},":\n",[203,956,957,960,962,964],{"class":115,"line":242},[203,958,959],{"class":208},"            from",[203,961,213],{"class":212},[203,963,216],{"class":208},[203,965,966],{"class":212}," QgsGeometry\n",[203,968,969,972,974,977,979,982,984],{"class":115,"line":254},[203,970,971],{"class":212},"            value ",[203,973,248],{"class":208},[203,975,976],{"class":212}," da.measureArea(QgsGeometry.fromPolygonXY([pts ",[203,978,865],{"class":208},[203,980,981],{"class":212}," [pts[",[203,983,51],{"class":328},[203,985,986],{"class":212},"]]]))\n",[203,988,989,992,994,997,999,1002,1004],{"class":115,"line":265},[203,990,991],{"class":212},"            text ",[203,993,248],{"class":208},[203,995,996],{"class":212}," da.formatArea(value, ",[203,998,668],{"class":328},[203,1000,1001],{"class":212},", QgsUnitTypes.AreaSquareMeters, ",[203,1003,756],{"class":328},[203,1005,284],{"class":212},[203,1007,1008,1010,1012,1014,1016,1018,1020,1022,1024,1027],{"class":115,"line":271},[203,1009,780],{"class":208},[203,1011,826],{"class":208},[203,1013,718],{"class":328},[203,1015,553],{"class":212},[203,1017,790],{"class":208},[203,1019,942],{"class":328},[203,1021,945],{"class":212},[203,1023,948],{"class":208},[203,1025,1026],{"class":328}," 2",[203,1028,954],{"class":212},[203,1030,1031,1033,1035],{"class":115,"line":287},[203,1032,971],{"class":212},[203,1034,248],{"class":208},[203,1036,1037],{"class":212}," da.measureLine(pts)\n",[203,1039,1040,1042,1044,1047,1049,1052,1054],{"class":115,"line":296},[203,1041,991],{"class":212},[203,1043,248],{"class":208},[203,1045,1046],{"class":212}," da.formatDistance(value, ",[203,1048,668],{"class":328},[203,1050,1051],{"class":212},", QgsUnitTypes.DistanceMeters, ",[203,1053,509],{"class":328},[203,1055,284],{"class":212},[203,1057,1058,1061],{"class":115,"line":301},[203,1059,1060],{"class":208},"        else",[203,1062,954],{"class":212},[203,1064,1065],{"class":115,"line":314},[203,1066,836],{"class":208},[203,1068,1069,1072,1075,1077,1080,1082,1085,1088,1090],{"class":115,"line":325},[203,1070,1071],{"class":212},"        iface.statusBarIface().showMessage((",[203,1073,1074],{"class":280},"\"Final: \"",[203,1076,634],{"class":208},[203,1078,1079],{"class":212}," final ",[203,1081,589],{"class":208},[203,1083,1084],{"class":280}," \"\"",[203,1086,1087],{"class":212},") ",[203,1089,865],{"class":208},[203,1091,1092],{"class":212}," text)\n",[203,1094,1095,1098],{"class":115,"line":595},[203,1096,1097],{"class":208},"        return",[203,1099,1100],{"class":212}," value\n",[203,1102,1103],{"class":115,"line":608},[203,1104,226],{"emptyLinePlaceholder":225},[203,1106,1107,1109,1112],{"class":115,"line":647},[203,1108,498],{"class":208},[203,1110,1111],{"class":235}," finish",[203,1113,1114],{"class":212},"(self):\n",[203,1116,1117,1120,1122,1124,1126,1128,1131,1135,1137,1139],{"class":115,"line":660},[203,1118,1119],{"class":212},"        value ",[203,1121,248],{"class":208},[203,1123,718],{"class":328},[203,1125,858],{"class":212},[203,1127,847],{"class":328},[203,1129,1130],{"class":212},".points, ",[203,1132,1134],{"class":1133},"s9osk","final",[203,1136,248],{"class":208},[203,1138,756],{"class":328},[203,1140,284],{"class":212},[203,1142,1143,1145,1147,1149,1151,1154,1157,1159,1162],{"class":115,"line":673},[203,1144,727],{"class":208},[203,1146,718],{"class":328},[203,1148,565],{"class":212},[203,1150,790],{"class":208},[203,1152,1153],{"class":212}," value ",[203,1155,1156],{"class":208},"is",[203,1158,826],{"class":208},[203,1160,1161],{"class":328}," None",[203,1163,954],{"class":212},[203,1165,1166,1168,1171,1174,1176,1178],{"class":115,"line":686},[203,1167,742],{"class":328},[203,1169,1170],{"class":212},".on_result(value, ",[203,1172,1173],{"class":328},"list",[203,1175,332],{"class":212},[203,1177,847],{"class":328},[203,1179,1180],{"class":212},".points))\n",[203,1182,1183,1185],{"class":115,"line":694},[203,1184,538],{"class":328},[203,1186,1187],{"class":212},".reset()\n",[203,1189,1190],{"class":115,"line":699},[203,1191,226],{"emptyLinePlaceholder":225},[203,1193,1194,1196,1199],{"class":115,"line":710},[203,1195,498],{"class":208},[203,1197,1198],{"class":235}," reset",[203,1200,1114],{"class":212},[203,1202,1203,1205,1207,1209],{"class":115,"line":724},[203,1204,538],{"class":328},[203,1206,678],{"class":212},[203,1208,248],{"class":208},[203,1210,683],{"class":212},[203,1212,1213,1215,1218,1220,1222,1224,1226],{"class":115,"line":739},[203,1214,538],{"class":328},[203,1216,1217],{"class":212},".band.reset(QgsWkbTypes.PolygonGeometry ",[203,1219,583],{"class":208},[203,1221,718],{"class":328},[203,1223,553],{"class":212},[203,1225,589],{"class":208},[203,1227,1228],{"class":212}," QgsWkbTypes.LineGeometry)\n",[203,1230,1231],{"class":115,"line":748},[203,1232,226],{"emptyLinePlaceholder":225},[203,1234,1235,1237,1240],{"class":115,"line":761},[203,1236,498],{"class":208},[203,1238,1239],{"class":235}," deactivate",[203,1241,1114],{"class":212},[203,1243,1244,1246],{"class":115,"line":777},[203,1245,538],{"class":328},[203,1247,1187],{"class":212},[203,1249,1250,1252],{"class":115,"line":798},[203,1251,524],{"class":328},[203,1253,1254],{"class":212},"().deactivate()\n",[14,1256,1257,1259,1260,1263,1264,1267,1268,1263,1271,1274,1275,1278],{},[348,1258,350],{}," ",[27,1261,1262],{},"measureLine"," and ",[27,1265,1266],{},"measureArea"," return metres and square metres on the ellipsoid. ",[27,1269,1270],{},"formatDistance",[27,1272,1273],{},"formatArea"," produce readable strings with units, switching to kilometres or hectares when the last argument allows automatic unit selection. The status bar is an unobtrusive place for the live value; the final value goes to a callback so the plugin decides what to do with it — fill a form field, compare with a threshold, log it. Resetting in ",[27,1276,1277],{},"deactivate"," clears the rubber band when the user switches tools.",[165,1280,1282],{"id":1281},"show-segment-lengths-on-the-canvas","Show segment lengths on the canvas",[14,1284,1285],{},"For routes with several legs — a pipe run from valve to valve, a walking route between stops — users want each segment's length, not just the total. Small text items placed at segment midpoints show them without cluttering a dialog.",[194,1287,1289],{"className":196,"code":1288,"language":198,"meta":199,"style":199},"from qgis.gui import QgsMapCanvasItem\nfrom qgis.PyQt.QtCore import QRectF, QPointF\nfrom qgis.PyQt.QtGui import QFont, QPen\n\nclass SegmentLabel(QgsMapCanvasItem):\n    def __init__(self, canvas, map_point, text):\n        super().__init__(canvas)\n        self.map_point, self.text = map_point, text\n        self.updatePosition()\n\n    def updatePosition(self):\n        self.prepareGeometryChange()\n        self.setPos(self.toCanvasCoordinates(self.map_point))\n\n    def boundingRect(self):\n        return QRectF(-40, -18, 80, 16)\n\n    def paint(self, painter, option=None, widget=None):\n        painter.setFont(QFont(\"Sans\", 8))\n        painter.setPen(QPen(QColor(\"#17211d\")))\n        painter.drawText(QRectF(-40, -18, 80, 16), Qt.AlignCenter, self.text)\n\ndef label_segments(canvas, pts, da):\n    items = []\n    for a, b in zip(pts, pts[1:]):\n        mid = QgsPointXY((a.x() + b.x()) \u002F 2, (a.y() + b.y()) \u002F 2)\n        length = da.measureLine(a, b)\n        items.append(SegmentLabel(canvas, mid, da.formatDistance(length, 1, QgsUnitTypes.DistanceMeters, False)))\n    return items\n",[27,1290,1291,1302,1313,1324,1328,1342,1351,1361,1378,1385,1389,1398,1405,1422,1426,1435,1466,1470,1493,1507,1518,1549,1553,1563,1572,1595,1629,1639,1652],{"__ignoreMap":199},[203,1292,1293,1295,1297,1299],{"class":115,"line":205},[203,1294,209],{"class":208},[203,1296,433],{"class":212},[203,1298,216],{"class":208},[203,1300,1301],{"class":212}," QgsMapCanvasItem\n",[203,1303,1304,1306,1308,1310],{"class":115,"line":222},[203,1305,209],{"class":208},[203,1307,468],{"class":212},[203,1309,216],{"class":208},[203,1311,1312],{"class":212}," QRectF, QPointF\n",[203,1314,1315,1317,1319,1321],{"class":115,"line":229},[203,1316,209],{"class":208},[203,1318,456],{"class":212},[203,1320,216],{"class":208},[203,1322,1323],{"class":212}," QFont, QPen\n",[203,1325,1326],{"class":115,"line":242},[203,1327,226],{"emptyLinePlaceholder":225},[203,1329,1330,1332,1335,1337,1340],{"class":115,"line":254},[203,1331,482],{"class":208},[203,1333,1334],{"class":235}," SegmentLabel",[203,1336,332],{"class":212},[203,1338,1339],{"class":235},"QgsMapCanvasItem",[203,1341,493],{"class":212},[203,1343,1344,1346,1348],{"class":115,"line":265},[203,1345,498],{"class":208},[203,1347,501],{"class":328},[203,1349,1350],{"class":212},"(self, canvas, map_point, text):\n",[203,1352,1353,1355,1357,1359],{"class":115,"line":271},[203,1354,524],{"class":328},[203,1356,527],{"class":212},[203,1358,530],{"class":328},[203,1360,533],{"class":212},[203,1362,1363,1365,1368,1370,1373,1375],{"class":115,"line":287},[203,1364,538],{"class":328},[203,1366,1367],{"class":212},".map_point, ",[203,1369,847],{"class":328},[203,1371,1372],{"class":212},".text ",[203,1374,248],{"class":208},[203,1376,1377],{"class":212}," map_point, text\n",[203,1379,1380,1382],{"class":115,"line":296},[203,1381,538],{"class":328},[203,1383,1384],{"class":212},".updatePosition()\n",[203,1386,1387],{"class":115,"line":301},[203,1388,226],{"emptyLinePlaceholder":225},[203,1390,1391,1393,1396],{"class":115,"line":314},[203,1392,498],{"class":208},[203,1394,1395],{"class":235}," updatePosition",[203,1397,1114],{"class":212},[203,1399,1400,1402],{"class":115,"line":325},[203,1401,538],{"class":328},[203,1403,1404],{"class":212},".prepareGeometryChange()\n",[203,1406,1407,1409,1412,1414,1417,1419],{"class":115,"line":595},[203,1408,538],{"class":328},[203,1410,1411],{"class":212},".setPos(",[203,1413,847],{"class":328},[203,1415,1416],{"class":212},".toCanvasCoordinates(",[203,1418,847],{"class":328},[203,1420,1421],{"class":212},".map_point))\n",[203,1423,1424],{"class":115,"line":608},[203,1425,226],{"emptyLinePlaceholder":225},[203,1427,1428,1430,1433],{"class":115,"line":647},[203,1429,498],{"class":208},[203,1431,1432],{"class":235}," boundingRect",[203,1434,1114],{"class":212},[203,1436,1437,1439,1442,1445,1448,1450,1452,1455,1457,1459,1461,1464],{"class":115,"line":660},[203,1438,1097],{"class":208},[203,1440,1441],{"class":212}," QRectF(",[203,1443,1444],{"class":208},"-",[203,1446,1447],{"class":328},"40",[203,1449,619],{"class":212},[203,1451,1444],{"class":208},[203,1453,1454],{"class":328},"18",[203,1456,619],{"class":212},[203,1458,398],{"class":328},[203,1460,619],{"class":212},[203,1462,1463],{"class":328},"16",[203,1465,284],{"class":212},[203,1467,1468],{"class":115,"line":673},[203,1469,226],{"emptyLinePlaceholder":225},[203,1471,1472,1474,1477,1480,1482,1484,1487,1489,1491],{"class":115,"line":686},[203,1473,498],{"class":208},[203,1475,1476],{"class":235}," paint",[203,1478,1479],{"class":212},"(self, painter, option",[203,1481,248],{"class":208},[203,1483,517],{"class":328},[203,1485,1486],{"class":212},", widget",[203,1488,248],{"class":208},[203,1490,517],{"class":328},[203,1492,493],{"class":212},[203,1494,1495,1498,1501,1503,1505],{"class":115,"line":694},[203,1496,1497],{"class":212},"        painter.setFont(QFont(",[203,1499,1500],{"class":280},"\"Sans\"",[203,1502,619],{"class":212},[203,1504,63],{"class":328},[203,1506,644],{"class":212},[203,1508,1509,1512,1515],{"class":115,"line":699},[203,1510,1511],{"class":212},"        painter.setPen(QPen(QColor(",[203,1513,1514],{"class":280},"\"#17211d\"",[203,1516,1517],{"class":212},")))\n",[203,1519,1520,1523,1525,1527,1529,1531,1533,1535,1537,1539,1541,1544,1546],{"class":115,"line":710},[203,1521,1522],{"class":212},"        painter.drawText(QRectF(",[203,1524,1444],{"class":208},[203,1526,1447],{"class":328},[203,1528,619],{"class":212},[203,1530,1444],{"class":208},[203,1532,1454],{"class":328},[203,1534,619],{"class":212},[203,1536,398],{"class":328},[203,1538,619],{"class":212},[203,1540,1463],{"class":328},[203,1542,1543],{"class":212},"), Qt.AlignCenter, ",[203,1545,847],{"class":328},[203,1547,1548],{"class":212},".text)\n",[203,1550,1551],{"class":115,"line":724},[203,1552,226],{"emptyLinePlaceholder":225},[203,1554,1555,1557,1560],{"class":115,"line":739},[203,1556,232],{"class":208},[203,1558,1559],{"class":235}," label_segments",[203,1561,1562],{"class":212},"(canvas, pts, da):\n",[203,1564,1565,1568,1570],{"class":115,"line":748},[203,1566,1567],{"class":212},"    items ",[203,1569,248],{"class":208},[203,1571,683],{"class":212},[203,1573,1574,1577,1580,1583,1586,1589,1592],{"class":115,"line":761},[203,1575,1576],{"class":208},"    for",[203,1578,1579],{"class":212}," a, b ",[203,1581,1582],{"class":208},"in",[203,1584,1585],{"class":328}," zip",[203,1587,1588],{"class":212},"(pts, pts[",[203,1590,1591],{"class":328},"1",[203,1593,1594],{"class":212},":]):\n",[203,1596,1597,1600,1602,1605,1607,1610,1613,1615,1618,1620,1623,1625,1627],{"class":115,"line":777},[203,1598,1599],{"class":212},"        mid ",[203,1601,248],{"class":208},[203,1603,1604],{"class":212}," QgsPointXY((a.x() ",[203,1606,865],{"class":208},[203,1608,1609],{"class":212}," b.x()) ",[203,1611,1612],{"class":208},"\u002F",[203,1614,1026],{"class":328},[203,1616,1617],{"class":212},", (a.y() ",[203,1619,865],{"class":208},[203,1621,1622],{"class":212}," b.y()) ",[203,1624,1612],{"class":208},[203,1626,1026],{"class":328},[203,1628,284],{"class":212},[203,1630,1631,1634,1636],{"class":115,"line":798},[203,1632,1633],{"class":212},"        length ",[203,1635,248],{"class":208},[203,1637,1638],{"class":212}," da.measureLine(a, b)\n",[203,1640,1641,1644,1646,1648,1650],{"class":115,"line":806},[203,1642,1643],{"class":212},"        items.append(SegmentLabel(canvas, mid, da.formatDistance(length, ",[203,1645,1591],{"class":328},[203,1647,1051],{"class":212},[203,1649,509],{"class":328},[203,1651,1517],{"class":212},[203,1653,1654,1656],{"class":115,"line":811},[203,1655,290],{"class":208},[203,1657,1658],{"class":212}," items\n",[14,1660,1661,1663,1664,1666,1667,1263,1670,1673,1674,1678],{},[348,1662,350],{}," Each label is a small canvas item anchored at a segment's midpoint in map coordinates, so it stays in place as the map pans and zooms. Measuring each pair of consecutive points with the same ",[27,1665,29],{}," keeps segment lengths consistent with the total. Store the returned items on the tool and remove them from the scene in ",[27,1668,1669],{},"reset()",[27,1671,1672],{},"deactivate()",", or they linger after the measurement ends; ",[21,1675,1677],{"href":1676},"\u002Fqgis-plugin-development\u002Fextending-qgis-with-custom-classes\u002Fdraw-custom-map-canvas-item-pyqgis\u002F","drawing a custom map canvas item"," covers the canvas item lifecycle.",[165,1680,1682],{"id":1681},"snap-to-features-while-measuring","Snap to features while measuring",[14,1684,1685],{},"Measuring \"from this building corner to that hydrant\" needs snapping. The canvas's snapping utilities snap mouse positions to features according to the project's snapping settings.",[194,1687,1689],{"className":196,"code":1688,"language":198,"meta":199,"style":199},"    def snapped(self, event):\n        match = self.canvas.snappingUtils().snapToMap(event.pos())\n        return match.point() if match.isValid() else self.toMapCoordinates(event.pos())\n",[27,1690,1691,1700,1712],{"__ignoreMap":199},[203,1692,1693,1695,1698],{"class":115,"line":205},[203,1694,498],{"class":208},[203,1696,1697],{"class":235}," snapped",[203,1699,707],{"class":212},[203,1701,1702,1705,1707,1709],{"class":115,"line":222},[203,1703,1704],{"class":212},"        match ",[203,1706,248],{"class":208},[203,1708,718],{"class":328},[203,1710,1711],{"class":212},".canvas.snappingUtils().snapToMap(event.pos())\n",[203,1713,1714,1716,1719,1721,1724,1726,1728],{"class":115,"line":229},[203,1715,1097],{"class":208},[203,1717,1718],{"class":212}," match.point() ",[203,1720,583],{"class":208},[203,1722,1723],{"class":212}," match.isValid() ",[203,1725,589],{"class":208},[203,1727,718],{"class":328},[203,1729,721],{"class":212},[14,1731,1732,1734,1735,1738,1739,1742,1743,1747],{},[348,1733,350],{}," Replacing ",[27,1736,1737],{},"self.toMapCoordinates(event.pos())"," with ",[27,1740,1741],{},"self.snapped(event)"," in both event handlers makes clicks and the preview snap to vertices and segments when snapping is enabled in the project. Using the project's snapping configuration means users control it with the familiar snapping toolbar. ",[21,1744,1746],{"href":1745},"\u002Fqgis-plugin-development\u002Fcustom-map-tools-and-canvas-interaction\u002Fsnap-to-features-with-qgssnappingutils-pyqgis\u002F","Snapping to features with QgsSnappingUtils"," covers custom snapping configurations and visual snap indicators.",[165,1749,1751],{"id":1750},"check-a-rule-with-the-measurement","Check a rule with the measurement",[14,1753,1754],{},"Many plugins measure to decide something: is a proposed building at least 3 m from the boundary, is a plot above the minimum size. A callback turns the measure tool into a checker.",[14,1756,1757],{},[34,1758,1761,1764,1767,1770,1777,1780,1784,1789,1793,1798,1801,1804,1807,1811,1814,1818],{"viewBox":1759,"role":37,"ariaLabel":1760,"xmlns":39},"0 0 760 196","A measured distance compared against a minimum setback, producing a pass or fail message and an optional record in the feature's attributes",[41,1762,1763],{},"From measurement to decision",[45,1765,1766],{},"The tool's result callback receives the measured distance. Compared with the rule, here a 3 metre minimum setback, it produces a pass or fail message in the message bar, and can write the value and verdict into the selected feature's attributes so the check is recorded.",[49,1768],{"x":51,"y":51,"width":52,"height":1769,"fill":54},"196",[56,1771,1772],{},[59,1773,1775],{"id":1774,"viewBox":62,"refX":63,"refY":64,"markerWidth":65,"markerHeight":65,"orient":66},"msRuleArrow",[68,1776],{"d":70,"fill":71},[73,1778,1779],{"x":75,"y":76,"style":77,"fill":78,"textAnchor":79},"Measure, compare, record",[49,1781],{"x":83,"y":1782,"width":119,"height":1783,"rx":63,"fill":105,"stroke":106,"style":89},"60","120",[73,1785,1788],{"x":1786,"y":1787,"style":94,"fill":106,"textAnchor":79},"124","110.78","measured",[73,1790,1792],{"x":1786,"y":1791,"style":99,"fill":100,"textAnchor":79},"136.78","3.42 m",[115,1794],{"x1":1795,"y1":1783,"x2":1796,"y2":1783,"stroke":71,"style":1797},"224","266","stroke-width:1.8;marker-end:url(#msRuleArrow)",[49,1799],{"x":1800,"y":1782,"width":406,"height":1783,"rx":63,"fill":129,"stroke":130,"style":89},"270",[73,1802,1803],{"x":75,"y":1787,"style":94,"fill":130,"textAnchor":79},"rule",[73,1805,1806],{"x":75,"y":1791,"style":99,"fill":100,"textAnchor":79},"minimum 3.0 m",[115,1808],{"x1":1809,"y1":1783,"x2":1810,"y2":1783,"stroke":71,"style":1797},"490","532",[49,1812],{"x":1813,"y":1782,"width":119,"height":1783,"rx":63,"fill":151,"stroke":152,"style":89},"536",[73,1815,1817],{"x":1816,"y":1787,"style":94,"fill":156,"textAnchor":79},"636","PASS",[73,1819,1820],{"x":1816,"y":1791,"style":99,"fill":100,"textAnchor":79},"message + record",[194,1822,1824],{"className":196,"code":1823,"language":198,"meta":199,"style":199},"MIN_SETBACK = 3.0\n\ndef check_setback(distance, points):\n    if distance >= MIN_SETBACK:\n        iface.messageBar().pushSuccess(\"Setback\", f\"{distance:.2f} m — meets the {MIN_SETBACK} m minimum\")\n    else:\n        iface.messageBar().pushCritical(\"Setback\", f\"{distance:.2f} m — below the {MIN_SETBACK} m minimum\")\n\ntool = MeasureTool(iface.mapCanvas(), area=False, on_result=check_setback)\niface.mapCanvas().setMapTool(tool)\n",[27,1825,1826,1837,1841,1851,1866,1905,1912,1942,1946,1973],{"__ignoreMap":199},[203,1827,1828,1831,1834],{"class":115,"line":205},[203,1829,1830],{"class":328},"MIN_SETBACK",[203,1832,1833],{"class":208}," =",[203,1835,1836],{"class":328}," 3.0\n",[203,1838,1839],{"class":115,"line":222},[203,1840,226],{"emptyLinePlaceholder":225},[203,1842,1843,1845,1848],{"class":115,"line":229},[203,1844,232],{"class":208},[203,1846,1847],{"class":235}," check_setback",[203,1849,1850],{"class":212},"(distance, points):\n",[203,1852,1853,1856,1859,1861,1864],{"class":115,"line":242},[203,1854,1855],{"class":208},"    if",[203,1857,1858],{"class":212}," distance ",[203,1860,948],{"class":208},[203,1862,1863],{"class":328}," MIN_SETBACK",[203,1865,954],{"class":212},[203,1867,1868,1871,1874,1876,1879,1882,1885,1888,1891,1894,1897,1900,1903],{"class":115,"line":254},[203,1869,1870],{"class":212},"        iface.messageBar().pushSuccess(",[203,1872,1873],{"class":280},"\"Setback\"",[203,1875,619],{"class":212},[203,1877,1878],{"class":208},"f",[203,1880,1881],{"class":280},"\"",[203,1883,1884],{"class":328},"{",[203,1886,1887],{"class":212},"distance",[203,1889,1890],{"class":208},":.2f",[203,1892,1893],{"class":328},"}",[203,1895,1896],{"class":280}," m — meets the ",[203,1898,1899],{"class":328},"{MIN_SETBACK}",[203,1901,1902],{"class":280}," m minimum\"",[203,1904,284],{"class":212},[203,1906,1907,1910],{"class":115,"line":265},[203,1908,1909],{"class":208},"    else",[203,1911,954],{"class":212},[203,1913,1914,1917,1919,1921,1923,1925,1927,1929,1931,1933,1936,1938,1940],{"class":115,"line":271},[203,1915,1916],{"class":212},"        iface.messageBar().pushCritical(",[203,1918,1873],{"class":280},[203,1920,619],{"class":212},[203,1922,1878],{"class":208},[203,1924,1881],{"class":280},[203,1926,1884],{"class":328},[203,1928,1887],{"class":212},[203,1930,1890],{"class":208},[203,1932,1893],{"class":328},[203,1934,1935],{"class":280}," m — below the ",[203,1937,1899],{"class":328},[203,1939,1902],{"class":280},[203,1941,284],{"class":212},[203,1943,1944],{"class":115,"line":287},[203,1945,226],{"emptyLinePlaceholder":225},[203,1947,1948,1951,1953,1956,1959,1961,1963,1965,1968,1970],{"class":115,"line":296},[203,1949,1950],{"class":212},"tool ",[203,1952,248],{"class":208},[203,1954,1955],{"class":212}," MeasureTool(iface.mapCanvas(), ",[203,1957,1958],{"class":1133},"area",[203,1960,248],{"class":208},[203,1962,509],{"class":328},[203,1964,619],{"class":212},[203,1966,1967],{"class":1133},"on_result",[203,1969,248],{"class":208},[203,1971,1972],{"class":212},"check_setback)\n",[203,1974,1975],{"class":115,"line":301},[203,1976,1977],{"class":212},"iface.mapCanvas().setMapTool(tool)\n",[14,1979,1980,1982,1983,1986,1987,1991],{},[348,1981,350],{}," The callback receives the final distance and the clicked points, so it can both judge and record. The message bar gives immediate, colour-coded feedback; for an audit trail, write the distance and verdict into an attribute of the selected feature through the edit buffer. The same structure handles area rules with ",[27,1984,1985],{},"area=True",". Wiring the tool to a toolbar button, as in ",[21,1988,1990],{"href":1989},"\u002Fqgis-plugin-development\u002Fplugin-boilerplate-structure\u002Fadd-toolbar-button-to-qgis-plugin\u002F","adding a toolbar button to a plugin",", makes it a one-click check.",[165,1993,1995],{"id":1994},"qgis-version-compatibility","QGIS version compatibility",[14,1997,1998,619,2000,619,2003,2005,2006,1263,2009,2012,2013,1263,2016,2019,2020,619,2023,2026],{},[27,1999,490],{},[27,2001,2002],{},"QgsRubberBand",[27,2004,29],{}," and snapping utilities work on QGIS 3.34 LTR, 3.40 LTR and QGIS 4. On QGIS 4, unit enums are ",[27,2007,2008],{},"Qgis.DistanceUnit.Meters",[27,2010,2011],{},"Qgis.AreaUnit.SquareMeters",", geometry types ",[27,2014,2015],{},"Qgis.GeometryType.Line",[27,2017,2018],{},"Polygon",", and Qt enums are scoped (",[27,2021,2022],{},"Qt.MouseButton.LeftButton",[27,2024,2025],{},"Qt.CursorShape.CrossCursor",").",[165,2028,2030],{"id":2029},"troubleshooting","Troubleshooting",[170,2032,2033,2042,2048,2056],{},[173,2034,2035,2038,2039,2041],{},[348,2036,2037],{},"Distances are too long in Web Mercator."," Planar measurement was used; configure ",[27,2040,29],{}," with source CRS and ellipsoid.",[173,2043,2044,2047],{},[348,2045,2046],{},"The preview does not follow the mouse."," The moving tip point was not added after each click.",[173,2049,2050,2053,2054,183],{},[348,2051,2052],{},"The rubber band stays after switching tools."," Reset it in ",[27,2055,1277],{},[173,2057,2058,2061],{},[348,2059,2060],{},"Snapping does not work."," Snapping is disabled in the project, or the tool uses unsnapped coordinates.",[165,2063,2065],{"id":2064},"conclusion","Conclusion",[14,2067,2068,2069,2071,2072,2074,2075,2077],{},"Configure ",[27,2070,29],{}," with the canvas CRS and project ellipsoid, collect clicks into a rubber band with a moving tip for preview, measure with ",[27,2073,1262],{}," or ",[27,2076,1266],{}," and format with automatic units, snap with the canvas's snapping utilities, report through a callback that can check rules and record results, and clean up when the tool is deactivated.",[165,2079,2081],{"id":2080},"frequently-asked-questions","Frequently Asked Questions",[14,2083,2084,2087,2088,183],{},[348,2085,2086],{},"Can I measure along existing features instead of clicking?","\nYes — measure the selected feature's geometry with the same ",[27,2089,29],{},[14,2091,2092,2095],{},[348,2093,2094],{},"How do I show segment lengths?","\nMeasure each consecutive pair and display them in a list or as canvas labels.",[14,2097,2098,2101],{},[348,2099,2100],{},"Does the tool work in a 3D view?","\nNo; 2D canvas map tools only.",[14,2103,2104,2107],{},[348,2105,2106],{},"Why does my result differ slightly from QGIS's measure tool?","\nDifferent ellipsoid or CRS settings; use the project ellipsoid and the canvas CRS as shown, and both tools agree.",[14,2109,2110,2113,2114,2117,2118,2120,2121,183],{},[348,2111,2112],{},"Can I measure in feet?","\nPass ",[27,2115,2116],{},"QgsUnitTypes.DistanceFeet"," to ",[27,2119,1270],{}," or convert with ",[27,2122,2123],{},"QgsUnitTypes.fromUnitToUnitFactor",[165,2125,2127],{"id":2126},"related","Related",[170,2129,2130,2135,2140,2146,2151],{},[173,2131,2132,2134],{},[21,2133,24],{"href":23}," — the guide this recipe belongs to",[173,2136,2137],{},[21,2138,2139],{"href":181},"Create a Custom Map Tool in PyQGIS",[173,2141,2142],{},[21,2143,2145],{"href":2144},"\u002Fqgis-plugin-development\u002Fcustom-map-tools-and-canvas-interaction\u002Fhighlight-feature-with-rubber-band-pyqgis\u002F","Highlight a Feature with a Rubber Band in PyQGIS",[173,2147,2148],{},[21,2149,2150],{"href":1745},"Snap to Features with QgsSnappingUtils",[173,2152,2153],{},[21,2154,2156],{"href":2155},"\u002Fspatial-data-processing-automation\u002Fgeometry-operations-and-predicates\u002Fcalculate-distance-between-features-pyqgis\u002F","Calculate Distance Between Features in PyQGIS",[2158,2159,2160],"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 .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}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}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":199,"searchDepth":222,"depth":222,"links":2162},[2163,2164,2165,2166,2167,2168,2169,2170,2171,2172,2173,2174],{"id":167,"depth":222,"text":168},{"id":186,"depth":222,"text":187},{"id":362,"depth":222,"text":363},{"id":889,"depth":222,"text":890},{"id":1281,"depth":222,"text":1282},{"id":1681,"depth":222,"text":1682},{"id":1750,"depth":222,"text":1751},{"id":1994,"depth":222,"text":1995},{"id":2029,"depth":222,"text":2030},{"id":2064,"depth":222,"text":2065},{"id":2080,"depth":222,"text":2081},{"id":2126,"depth":222,"text":2127},"Write a custom map tool that measures distances and areas correctly — clicking vertices with a rubber band preview, ellipsoidal measurement with QgsDistanceArea in any CRS, live readouts while the mouse moves, unit conversion, snapping, and showing results in the message bar or a dock.","md",{"slug":2178,"type":2179,"breadcrumb":2180,"datePublished":2181,"dateModified":2181},"build-measure-tool-with-qgsdistancearea-pyqgis","article","Build a Measure Tool","2026-10-02","\u002Fqgis-plugin-development\u002Fcustom-map-tools-and-canvas-interaction\u002Fbuild-measure-tool-with-qgsdistancearea-pyqgis",{"title":5,"description":2175},"qgis-plugin-development\u002Fcustom-map-tools-and-canvas-interaction\u002Fbuild-measure-tool-with-qgsdistancearea-pyqgis\u002Findex","9IyxtuESZaDYMMEewufgwb1od7i3oc_6U4hA53Pj4Rw",1790966257104]