[{"data":1,"prerenderedAt":2121},["ShallowReactive",2],{"doc:\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002Fwrite-feature-based-processing-algorithm-pyqgis":3},{"id":4,"title":5,"body":6,"description":2110,"extension":2111,"meta":2112,"navigation":281,"path":2117,"seo":2118,"stem":2119,"__hash__":2120},"docs\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002Fwrite-feature-based-processing-algorithm-pyqgis\u002Findex.md","Write a Feature-Based Processing Algorithm in PyQGIS",{"type":7,"value":8,"toc":2098},"minimark",[9,13,31,40,189,194,216,220,223,816,850,854,857,972,1427,1454,1458,1465,1520,1530,1588,1592,1595,1904,1920,1924,1952,1956,2001,2005,2022,2026,2032,2041,2055,2061,2065,2094],[10,11,5],"h1",{"id":12},"write-a-feature-based-processing-algorithm-in-pyqgis",[14,15,16,17,21,22,25,26,30],"p",{},"A large share of Processing algorithms do the same thing: read each feature, change it — its geometry, an attribute, both — and write it out. Buffer, centroid, add a field, round coordinates, clean text. Writing those as a full ",[18,19,20],"code",{},"QgsProcessingAlgorithm"," means repeating the same source, sink, loop, progress and cancellation code every time. ",[18,23,24],{},"QgsProcessingFeatureBasedAlgorithm"," owns all of that. You implement one method that transforms a single feature and a few methods that describe the output, and you get the input and output parameters, the loop, progress, cancellation, selected-features support and — if you opt in — the ability to run ",[27,28,29],"em",{},"in place"," on an editable layer.",[14,32,33,34,39],{},"This recipe belongs to ",[35,36,38],"a",{"href":37},"\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002F","Processing Provider Plugins for QGIS",". It writes a feature-based algorithm that normalises text attributes and one that transforms geometry, declares output fields and geometry types correctly, adds parameters, and enables in-place editing.",[14,41,42],{},[43,44,49,53,57,64,81,90,100,106,111,115,120,126,135,140,145,149,153,157,160,164,167,170,172,179,184],"svg",{"viewBox":45,"role":46,"ariaLabel":47,"xmlns":48},"0 0 760 300","img","What the feature-based base class does and what you write: the base class declares INPUT and OUTPUT, creates the sink, loops over features, reports progress and handles cancellation; you implement processFeature and describe output fields and geometry type","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[50,51,52],"title",{},"You write the middle, the base class writes the loop",[54,55,56],"desc",{},"A pipeline from an input source to an output sink. The base class owns declaring INPUT and OUTPUT parameters, preparing the source, creating the sink with the fields from outputFields and the type from outputWkbType, iterating features, progress, cancellation and selected-features-only. In the middle, your processFeature receives one feature and returns a list of zero, one or several features. Optional hooks are initParameters, prepareAlgorithm and supportInPlaceEdit.",[58,59],"rect",{"x":60,"y":60,"width":61,"height":62,"fill":63},"0","760","300","#f6f3ea",[65,66,67],"defs",{},[68,69,76],"marker",{"id":70,"viewBox":71,"refX":72,"refY":73,"markerWidth":74,"markerHeight":74,"orient":75},"fbaFlowArrow","0 0 10 10","8","5","7","auto-start-reverse",[77,78],"path",{"d":79,"fill":80},"M0 0 L10 5 L0 10 z","#2f3b35",[82,83,89],"text",{"x":84,"y":85,"style":86,"fill":87,"textAnchor":88},"380","28","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","One feature at a time, everything else inherited",[58,91],{"x":92,"y":93,"width":94,"height":95,"rx":96,"fill":97,"stroke":98,"style":99},"24","60","150","120","10","#fffdf7","#59645f","stroke-width:2",[82,101,105],{"x":102,"y":103,"style":104,"fill":87,"textAnchor":88},"99","92","text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif","INPUT",[82,107,110],{"x":102,"y":108,"style":109,"fill":80,"textAnchor":88},"116","text-anchor:middle;font-size:10px;font-family:sans-serif","source, selection,",[82,112,114],{"x":102,"y":113,"style":109,"fill":80,"textAnchor":88},"134","iteration, progress",[82,116,119],{"x":102,"y":117,"style":118,"fill":98,"textAnchor":88},"162","text-anchor:middle;font-size:9.5px;font-family:sans-serif","base class",[121,122],"line",{"x1":123,"y1":95,"x2":124,"y2":95,"stroke":80,"style":125},"174","226","stroke-width:2;marker-end:url(#fbaFlowArrow)",[58,127],{"x":128,"y":129,"width":130,"height":131,"rx":96,"fill":132,"stroke":133,"style":134},"234","48","292","144","#eef7f4","#0f766e","stroke-width:2.5",[82,136,139],{"x":84,"y":137,"style":138,"fill":133,"textAnchor":88},"76","text-anchor:middle;font-size:11.5px;font-weight:bold;font-family:sans-serif","processFeature(feature, …)",[82,141,144],{"x":84,"y":142,"style":143,"fill":80,"textAnchor":88},"104","text-anchor:middle;font-size:10.5px;font-family:sans-serif","change geometry or attributes",[82,146,148],{"x":84,"y":147,"style":143,"fill":80,"textAnchor":88},"128","return [] · [f] · [f1, f2, …]",[82,150,152],{"x":84,"y":151,"style":118,"fill":98,"textAnchor":88},"166","you write this",[121,154],{"x1":155,"y1":95,"x2":156,"y2":95,"stroke":80,"style":125},"526","578",[58,158],{"x":159,"y":93,"width":94,"height":95,"rx":96,"fill":97,"stroke":98,"style":99},"586",[82,161,163],{"x":162,"y":103,"style":104,"fill":87,"textAnchor":88},"661","OUTPUT",[82,165,166],{"x":162,"y":108,"style":109,"fill":80,"textAnchor":88},"sink created from",[82,168,169],{"x":162,"y":113,"style":109,"fill":80,"textAnchor":88},"your field\u002Ftype hooks",[82,171,119],{"x":162,"y":117,"style":118,"fill":98,"textAnchor":88},[58,173],{"x":95,"y":174,"width":175,"height":176,"rx":96,"fill":177,"stroke":178,"style":99},"220","520","56","#fdf2e2","#b45309",[82,180,183],{"x":84,"y":181,"style":182,"fill":178,"textAnchor":88},"244","text-anchor:middle;font-size:10.5px;font-weight:bold;font-family:sans-serif","optional hooks",[82,185,188],{"x":84,"y":186,"style":187,"fill":80,"textAnchor":88},"264","text-anchor:middle;font-size:10px;font-family:monospace","initParameters · prepareAlgorithm · outputFields · outputWkbType · supportInPlaceEdit",[190,191,193],"h2",{"id":192},"prerequisites","Prerequisites",[195,196,197,205,213],"ul",{},[198,199,200,204],"li",{},[201,202,203],"strong",{},"QGIS 3.40 LTR"," or newer, or the QGIS 4 series.",[198,206,207,208,212],{},"A provider to register the algorithm with, as in ",[35,209,211],{"href":210},"\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002Fregister-processing-provider-in-plugin\u002F","registering a Processing provider in a plugin",". For experiments, a provider in the Python console is enough.",[198,214,215],{},"A transformation that works on one feature at a time. Anything that needs to see several features together — dissolving, clustering, nearest neighbours — belongs in a normal algorithm.",[190,217,219],{"id":218},"an-attribute-transformation","An attribute transformation",[14,221,222],{},"This algorithm trims whitespace, collapses repeated spaces and applies consistent capitalisation to chosen text fields. The feature passes through with its geometry untouched.",[224,225,230],"pre",{"className":226,"code":227,"language":228,"meta":229,"style":229},"language-python shiki shiki-themes github-dark","import re\nfrom qgis.core import (\n    QgsProcessingFeatureBasedAlgorithm, QgsProcessingParameterField,\n    QgsProcessingParameterEnum, QgsProcessing,\n)\n\nclass NormaliseText(QgsProcessingFeatureBasedAlgorithm):\n    FIELDS = \"FIELDS\"\n    CASE = \"CASE\"\n    CASES = [\"Leave case\", \"UPPER\", \"lower\", \"Title Case\"]\n\n    def name(self): return \"normalise_text\"\n    def displayName(self): return \"Normalise text fields\"\n    def group(self): return \"Data cleaning\"\n    def groupId(self): return \"data_cleaning\"\n    def outputName(self): return \"Normalised\"\n    def createInstance(self): return NormaliseText()\n    def inputLayerTypes(self): return [QgsProcessing.TypeVector]\n\n    def initParameters(self, config=None):\n        self.addParameter(QgsProcessingParameterField(\n            self.FIELDS, \"Text fields to normalise\", parentLayerParameterName=\"INPUT\",\n            type=QgsProcessingParameterField.String, allowMultiple=True))\n        self.addParameter(QgsProcessingParameterEnum(\n            self.CASE, \"Capitalisation\", options=self.CASES, defaultValue=0))\n\n    def prepareAlgorithm(self, parameters, context, feedback):\n        self.field_names = self.parameterAsFields(parameters, self.FIELDS, context)\n        self.case = self.parameterAsEnum(parameters, self.CASE, context)\n        return True\n\n    def processFeature(self, feature, context, feedback):\n        for name in self.field_names:\n            value = feature[name]\n            if not isinstance(value, str):\n                continue\n            value = re.sub(r\"\\s+\", \" \", value).strip()\n            value = [value, value.upper(), value.lower(), value.title()][self.case]\n            feature[name] = value\n        return [feature]\n","python","",[18,231,232,244,258,264,270,276,283,301,315,326,359,364,382,397,412,427,442,457,472,477,496,505,536,558,566,607,612,623,648,671,680,685,696,713,724,744,750,782,797,808],{"__ignoreMap":229},[233,234,236,240],"span",{"class":121,"line":235},1,[233,237,239],{"class":238},"snl16","import",[233,241,243],{"class":242},"s95oV"," re\n",[233,245,247,250,253,255],{"class":121,"line":246},2,[233,248,249],{"class":238},"from",[233,251,252],{"class":242}," qgis.core ",[233,254,239],{"class":238},[233,256,257],{"class":242}," (\n",[233,259,261],{"class":121,"line":260},3,[233,262,263],{"class":242},"    QgsProcessingFeatureBasedAlgorithm, QgsProcessingParameterField,\n",[233,265,267],{"class":121,"line":266},4,[233,268,269],{"class":242},"    QgsProcessingParameterEnum, QgsProcessing,\n",[233,271,273],{"class":121,"line":272},5,[233,274,275],{"class":242},")\n",[233,277,279],{"class":121,"line":278},6,[233,280,282],{"emptyLinePlaceholder":281},true,"\n",[233,284,286,289,293,296,298],{"class":121,"line":285},7,[233,287,288],{"class":238},"class",[233,290,292],{"class":291},"svObZ"," NormaliseText",[233,294,295],{"class":242},"(",[233,297,24],{"class":291},[233,299,300],{"class":242},"):\n",[233,302,304,308,311],{"class":121,"line":303},8,[233,305,307],{"class":306},"sDLfK","    FIELDS",[233,309,310],{"class":238}," =",[233,312,314],{"class":313},"sU2Wk"," \"FIELDS\"\n",[233,316,318,321,323],{"class":121,"line":317},9,[233,319,320],{"class":306},"    CASE",[233,322,310],{"class":238},[233,324,325],{"class":313}," \"CASE\"\n",[233,327,329,332,334,337,340,343,346,348,351,353,356],{"class":121,"line":328},10,[233,330,331],{"class":306},"    CASES",[233,333,310],{"class":238},[233,335,336],{"class":242}," [",[233,338,339],{"class":313},"\"Leave case\"",[233,341,342],{"class":242},", ",[233,344,345],{"class":313},"\"UPPER\"",[233,347,342],{"class":242},[233,349,350],{"class":313},"\"lower\"",[233,352,342],{"class":242},[233,354,355],{"class":313},"\"Title Case\"",[233,357,358],{"class":242},"]\n",[233,360,362],{"class":121,"line":361},11,[233,363,282],{"emptyLinePlaceholder":281},[233,365,367,370,373,376,379],{"class":121,"line":366},12,[233,368,369],{"class":238},"    def",[233,371,372],{"class":291}," name",[233,374,375],{"class":242},"(self): ",[233,377,378],{"class":238},"return",[233,380,381],{"class":313}," \"normalise_text\"\n",[233,383,385,387,390,392,394],{"class":121,"line":384},13,[233,386,369],{"class":238},[233,388,389],{"class":291}," displayName",[233,391,375],{"class":242},[233,393,378],{"class":238},[233,395,396],{"class":313}," \"Normalise text fields\"\n",[233,398,400,402,405,407,409],{"class":121,"line":399},14,[233,401,369],{"class":238},[233,403,404],{"class":291}," group",[233,406,375],{"class":242},[233,408,378],{"class":238},[233,410,411],{"class":313}," \"Data cleaning\"\n",[233,413,415,417,420,422,424],{"class":121,"line":414},15,[233,416,369],{"class":238},[233,418,419],{"class":291}," groupId",[233,421,375],{"class":242},[233,423,378],{"class":238},[233,425,426],{"class":313}," \"data_cleaning\"\n",[233,428,430,432,435,437,439],{"class":121,"line":429},16,[233,431,369],{"class":238},[233,433,434],{"class":291}," outputName",[233,436,375],{"class":242},[233,438,378],{"class":238},[233,440,441],{"class":313}," \"Normalised\"\n",[233,443,445,447,450,452,454],{"class":121,"line":444},17,[233,446,369],{"class":238},[233,448,449],{"class":291}," createInstance",[233,451,375],{"class":242},[233,453,378],{"class":238},[233,455,456],{"class":242}," NormaliseText()\n",[233,458,460,462,465,467,469],{"class":121,"line":459},18,[233,461,369],{"class":238},[233,463,464],{"class":291}," inputLayerTypes",[233,466,375],{"class":242},[233,468,378],{"class":238},[233,470,471],{"class":242}," [QgsProcessing.TypeVector]\n",[233,473,475],{"class":121,"line":474},19,[233,476,282],{"emptyLinePlaceholder":281},[233,478,480,482,485,488,491,494],{"class":121,"line":479},20,[233,481,369],{"class":238},[233,483,484],{"class":291}," initParameters",[233,486,487],{"class":242},"(self, config",[233,489,490],{"class":238},"=",[233,492,493],{"class":306},"None",[233,495,300],{"class":242},[233,497,499,502],{"class":121,"line":498},21,[233,500,501],{"class":306},"        self",[233,503,504],{"class":242},".addParameter(QgsProcessingParameterField(\n",[233,506,508,511,514,517,519,522,524,528,530,533],{"class":121,"line":507},22,[233,509,510],{"class":306},"            self",[233,512,513],{"class":242},".",[233,515,516],{"class":306},"FIELDS",[233,518,342],{"class":242},[233,520,521],{"class":313},"\"Text fields to normalise\"",[233,523,342],{"class":242},[233,525,527],{"class":526},"s9osk","parentLayerParameterName",[233,529,490],{"class":238},[233,531,532],{"class":313},"\"INPUT\"",[233,534,535],{"class":242},",\n",[233,537,539,542,544,547,550,552,555],{"class":121,"line":538},23,[233,540,541],{"class":526},"            type",[233,543,490],{"class":238},[233,545,546],{"class":242},"QgsProcessingParameterField.String, ",[233,548,549],{"class":526},"allowMultiple",[233,551,490],{"class":238},[233,553,554],{"class":306},"True",[233,556,557],{"class":242},"))\n",[233,559,561,563],{"class":121,"line":560},24,[233,562,501],{"class":306},[233,564,565],{"class":242},".addParameter(QgsProcessingParameterEnum(\n",[233,567,569,571,573,576,578,581,583,586,588,591,593,596,598,601,603,605],{"class":121,"line":568},25,[233,570,510],{"class":306},[233,572,513],{"class":242},[233,574,575],{"class":306},"CASE",[233,577,342],{"class":242},[233,579,580],{"class":313},"\"Capitalisation\"",[233,582,342],{"class":242},[233,584,585],{"class":526},"options",[233,587,490],{"class":238},[233,589,590],{"class":306},"self",[233,592,513],{"class":242},[233,594,595],{"class":306},"CASES",[233,597,342],{"class":242},[233,599,600],{"class":526},"defaultValue",[233,602,490],{"class":238},[233,604,60],{"class":306},[233,606,557],{"class":242},[233,608,610],{"class":121,"line":609},26,[233,611,282],{"emptyLinePlaceholder":281},[233,613,615,617,620],{"class":121,"line":614},27,[233,616,369],{"class":238},[233,618,619],{"class":291}," prepareAlgorithm",[233,621,622],{"class":242},"(self, parameters, context, feedback):\n",[233,624,626,628,631,633,636,639,641,643,645],{"class":121,"line":625},28,[233,627,501],{"class":306},[233,629,630],{"class":242},".field_names ",[233,632,490],{"class":238},[233,634,635],{"class":306}," self",[233,637,638],{"class":242},".parameterAsFields(parameters, ",[233,640,590],{"class":306},[233,642,513],{"class":242},[233,644,516],{"class":306},[233,646,647],{"class":242},", context)\n",[233,649,651,653,656,658,660,663,665,667,669],{"class":121,"line":650},29,[233,652,501],{"class":306},[233,654,655],{"class":242},".case ",[233,657,490],{"class":238},[233,659,635],{"class":306},[233,661,662],{"class":242},".parameterAsEnum(parameters, ",[233,664,590],{"class":306},[233,666,513],{"class":242},[233,668,575],{"class":306},[233,670,647],{"class":242},[233,672,674,677],{"class":121,"line":673},30,[233,675,676],{"class":238},"        return",[233,678,679],{"class":306}," True\n",[233,681,683],{"class":121,"line":682},31,[233,684,282],{"emptyLinePlaceholder":281},[233,686,688,690,693],{"class":121,"line":687},32,[233,689,369],{"class":238},[233,691,692],{"class":291}," processFeature",[233,694,695],{"class":242},"(self, feature, context, feedback):\n",[233,697,699,702,705,708,710],{"class":121,"line":698},33,[233,700,701],{"class":238},"        for",[233,703,704],{"class":242}," name ",[233,706,707],{"class":238},"in",[233,709,635],{"class":306},[233,711,712],{"class":242},".field_names:\n",[233,714,716,719,721],{"class":121,"line":715},34,[233,717,718],{"class":242},"            value ",[233,720,490],{"class":238},[233,722,723],{"class":242}," feature[name]\n",[233,725,727,730,733,736,739,742],{"class":121,"line":726},35,[233,728,729],{"class":238},"            if",[233,731,732],{"class":238}," not",[233,734,735],{"class":306}," isinstance",[233,737,738],{"class":242},"(value, ",[233,740,741],{"class":306},"str",[233,743,300],{"class":242},[233,745,747],{"class":121,"line":746},36,[233,748,749],{"class":238},"                continue\n",[233,751,753,755,757,760,763,766,769,772,774,776,779],{"class":121,"line":752},37,[233,754,718],{"class":242},[233,756,490],{"class":238},[233,758,759],{"class":242}," re.sub(",[233,761,762],{"class":238},"r",[233,764,765],{"class":313},"\"",[233,767,768],{"class":306},"\\s",[233,770,771],{"class":238},"+",[233,773,765],{"class":313},[233,775,342],{"class":242},[233,777,778],{"class":313},"\" \"",[233,780,781],{"class":242},", value).strip()\n",[233,783,785,787,789,792,794],{"class":121,"line":784},38,[233,786,718],{"class":242},[233,788,490],{"class":238},[233,790,791],{"class":242}," [value, value.upper(), value.lower(), value.title()][",[233,793,590],{"class":306},[233,795,796],{"class":242},".case]\n",[233,798,800,803,805],{"class":121,"line":799},39,[233,801,802],{"class":242},"            feature[name] ",[233,804,490],{"class":238},[233,806,807],{"class":242}," value\n",[233,809,811,813],{"class":121,"line":810},40,[233,812,676],{"class":238},[233,814,815],{"class":242}," [feature]\n",[14,817,818,821,822,825,826,828,829,831,832,834,835,838,839,841,842,845,846,849],{},[201,819,820],{},"Breakdown:"," ",[18,823,824],{},"initParameters"," adds parameters beyond the inherited ",[18,827,105],{}," and ",[18,830,163],{},"; the field parameter links to ",[18,833,105],{}," by name, which the base class creates. ",[18,836,837],{},"prepareAlgorithm"," runs once before the loop, so parameter parsing happens once, not per feature — storing the results on ",[18,840,590],{}," is safe because every run gets a fresh instance from ",[18,843,844],{},"createInstance",". ",[18,847,848],{},"processFeature"," receives each feature and returns a list: here always one feature, modified in place. Non-string values, including nulls, are skipped rather than converted, so numbers and empty cells survive untouched. Because output fields and geometry type are unchanged, no other methods are needed.",[190,851,853],{"id":852},"a-geometry-transformation-with-a-new-field","A geometry transformation with a new field",[14,855,856],{},"When the algorithm changes the geometry type or adds fields, it must say so, because the base class creates the output sink before it sees any feature.",[14,858,859],{},[43,860,863,866,869,872,879,883,888,893,897,901,908,912,917,921,925,927,931,935,941,944,950,955,958,962,968],{"viewBox":861,"role":46,"ariaLabel":862,"xmlns":48},"0 0 760 256","Output schema hooks: outputWkbType maps the input geometry type to the output type, outputFields maps input fields to output fields, and both are called before any feature is processed, so returned features must match them exactly",[50,864,865],{},"Describe the output before producing it",[54,867,868],{},"An input layer of MultiPolygon features with fields id and name. outputWkbType maps MultiPolygon to Point, because the algorithm produces label points. outputFields appends inner_distance as a double. The sink is created with Point geometry and fields id, name and inner_distance. Every feature returned from processFeature must have a point geometry and exactly those three attributes, or the sink rejects it.",[58,870],{"x":60,"y":60,"width":61,"height":871,"fill":63},"256",[65,873,874],{},[68,875,877],{"id":876,"viewBox":71,"refX":72,"refY":73,"markerWidth":74,"markerHeight":74,"orient":75},"fbaSchemaArrow",[77,878],{"d":79,"fill":80},[82,880,882],{"x":84,"y":881,"style":86,"fill":87,"textAnchor":88},"26","The sink is built from your hooks, not from features",[58,884],{"x":92,"y":885,"width":886,"height":887,"rx":96,"fill":97,"stroke":98,"style":99},"52","200","130",[82,889,892],{"x":890,"y":891,"style":104,"fill":87,"textAnchor":88},"124","78","input",[82,894,896],{"x":890,"y":895,"style":187,"fill":80,"textAnchor":88},"106","MultiPolygon",[82,898,900],{"x":890,"y":899,"style":187,"fill":80,"textAnchor":88},"132","id, name",[121,902],{"x1":903,"y1":904,"x2":905,"y2":906,"stroke":80,"style":907},"224","100","276","84","stroke-width:1.6;marker-end:url(#fbaSchemaArrow)",[121,909],{"x1":903,"y1":910,"x2":905,"y2":911,"stroke":80,"style":907},"136","152",[58,913],{"x":914,"y":885,"width":915,"height":916,"rx":96,"fill":132,"stroke":133,"style":99},"284","210","58",[82,918,920],{"x":919,"y":137,"style":187,"fill":133,"textAnchor":88},"389","outputWkbType",[82,922,924],{"x":919,"y":923,"style":109,"fill":80,"textAnchor":88},"96","MultiPolygon → Point",[58,926],{"x":914,"y":890,"width":915,"height":916,"rx":96,"fill":132,"stroke":133,"style":99},[82,928,930],{"x":919,"y":929,"style":187,"fill":133,"textAnchor":88},"148","outputFields",[82,932,934],{"x":919,"y":933,"style":109,"fill":80,"textAnchor":88},"168","+ inner_distance",[121,936],{"x1":937,"y1":938,"x2":939,"y2":940,"stroke":80,"style":907},"494","81","546","108",[121,942],{"x1":937,"y1":943,"x2":939,"y2":147,"stroke":80,"style":907},"153",[58,945],{"x":946,"y":885,"width":947,"height":887,"rx":96,"fill":948,"stroke":949,"style":134},"554","182","#e8efe6","#15803d",[82,951,954],{"x":952,"y":891,"style":104,"fill":953,"textAnchor":88},"645","#166534","output sink",[82,956,957],{"x":952,"y":895,"style":187,"fill":80,"textAnchor":88},"Point",[82,959,961],{"x":952,"y":899,"style":960,"fill":80,"textAnchor":88},"text-anchor:middle;font-size:9.5px;font-family:monospace","id, name, inner_distance",[58,963],{"x":95,"y":964,"width":175,"height":965,"rx":72,"fill":177,"stroke":966,"style":967},"206","34","#b91c1c","stroke-width:1.8",[82,969,971],{"x":84,"y":970,"style":143,"fill":80,"textAnchor":88},"228","features that do not match the declared schema are rejected by the sink",[224,973,975],{"className":226,"code":974,"language":228,"meta":229,"style":229},"from qgis.core import (\n    QgsField, QgsFields, QgsWkbTypes, QgsProcessingParameterDistance,\n    QgsProcessingException, QgsFeatureSink,\n)\nfrom qgis.PyQt.QtCore import QMetaType\n\nclass InteriorLabelPoints(QgsProcessingFeatureBasedAlgorithm):\n    TOLERANCE = \"TOLERANCE\"\n\n    def name(self): return \"interior_label_points\"\n    def displayName(self): return \"Interior label points\"\n    def group(self): return \"Cartography helpers\"\n    def groupId(self): return \"carto_helpers\"\n    def outputName(self): return \"Label points\"\n    def createInstance(self): return InteriorLabelPoints()\n    def inputLayerTypes(self): return [QgsProcessing.TypeVectorPolygon]\n\n    def initParameters(self, config=None):\n        self.addParameter(QgsProcessingParameterDistance(\n            self.TOLERANCE, \"Precision\", defaultValue=1.0, parentParameterName=\"INPUT\",\n            minValue=0.001))\n\n    def outputWkbType(self, input_wkb_type):\n        return QgsWkbTypes.Point\n\n    def outputFields(self, input_fields):\n        fields = QgsFields(input_fields)\n        fields.append(QgsField(\"inner_distance\", QMetaType.Type.Double))\n        return fields\n\n    def prepareAlgorithm(self, parameters, context, feedback):\n        self.tolerance = self.parameterAsDouble(parameters, self.TOLERANCE, context)\n        return True\n\n    def processFeature(self, feature, context, feedback):\n        geom = feature.geometry()\n        if geom.isEmpty():\n            feedback.reportError(f\"feature {feature.id()} has no geometry\", False)\n            return []\n        point = geom.poleOfInaccessibility(self.tolerance)\n        label_point, distance = point\n        feature.setGeometry(label_point)\n        feature.setAttributes(feature.attributes() + [distance])\n        return [feature]\n",[18,976,977,987,992,997,1001,1013,1017,1030,1040,1044,1057,1070,1083,1096,1109,1122,1135,1139,1153,1160,1194,1206,1210,1220,1227,1231,1241,1251,1262,1269,1273,1281,1303,1309,1313,1321,1331,1339,1369,1377,1392,1403,1409,1420],{"__ignoreMap":229},[233,978,979,981,983,985],{"class":121,"line":235},[233,980,249],{"class":238},[233,982,252],{"class":242},[233,984,239],{"class":238},[233,986,257],{"class":242},[233,988,989],{"class":121,"line":246},[233,990,991],{"class":242},"    QgsField, QgsFields, QgsWkbTypes, QgsProcessingParameterDistance,\n",[233,993,994],{"class":121,"line":260},[233,995,996],{"class":242},"    QgsProcessingException, QgsFeatureSink,\n",[233,998,999],{"class":121,"line":266},[233,1000,275],{"class":242},[233,1002,1003,1005,1008,1010],{"class":121,"line":272},[233,1004,249],{"class":238},[233,1006,1007],{"class":242}," qgis.PyQt.QtCore ",[233,1009,239],{"class":238},[233,1011,1012],{"class":242}," QMetaType\n",[233,1014,1015],{"class":121,"line":278},[233,1016,282],{"emptyLinePlaceholder":281},[233,1018,1019,1021,1024,1026,1028],{"class":121,"line":285},[233,1020,288],{"class":238},[233,1022,1023],{"class":291}," InteriorLabelPoints",[233,1025,295],{"class":242},[233,1027,24],{"class":291},[233,1029,300],{"class":242},[233,1031,1032,1035,1037],{"class":121,"line":303},[233,1033,1034],{"class":306},"    TOLERANCE",[233,1036,310],{"class":238},[233,1038,1039],{"class":313}," \"TOLERANCE\"\n",[233,1041,1042],{"class":121,"line":317},[233,1043,282],{"emptyLinePlaceholder":281},[233,1045,1046,1048,1050,1052,1054],{"class":121,"line":328},[233,1047,369],{"class":238},[233,1049,372],{"class":291},[233,1051,375],{"class":242},[233,1053,378],{"class":238},[233,1055,1056],{"class":313}," \"interior_label_points\"\n",[233,1058,1059,1061,1063,1065,1067],{"class":121,"line":361},[233,1060,369],{"class":238},[233,1062,389],{"class":291},[233,1064,375],{"class":242},[233,1066,378],{"class":238},[233,1068,1069],{"class":313}," \"Interior label points\"\n",[233,1071,1072,1074,1076,1078,1080],{"class":121,"line":366},[233,1073,369],{"class":238},[233,1075,404],{"class":291},[233,1077,375],{"class":242},[233,1079,378],{"class":238},[233,1081,1082],{"class":313}," \"Cartography helpers\"\n",[233,1084,1085,1087,1089,1091,1093],{"class":121,"line":384},[233,1086,369],{"class":238},[233,1088,419],{"class":291},[233,1090,375],{"class":242},[233,1092,378],{"class":238},[233,1094,1095],{"class":313}," \"carto_helpers\"\n",[233,1097,1098,1100,1102,1104,1106],{"class":121,"line":399},[233,1099,369],{"class":238},[233,1101,434],{"class":291},[233,1103,375],{"class":242},[233,1105,378],{"class":238},[233,1107,1108],{"class":313}," \"Label points\"\n",[233,1110,1111,1113,1115,1117,1119],{"class":121,"line":414},[233,1112,369],{"class":238},[233,1114,449],{"class":291},[233,1116,375],{"class":242},[233,1118,378],{"class":238},[233,1120,1121],{"class":242}," InteriorLabelPoints()\n",[233,1123,1124,1126,1128,1130,1132],{"class":121,"line":429},[233,1125,369],{"class":238},[233,1127,464],{"class":291},[233,1129,375],{"class":242},[233,1131,378],{"class":238},[233,1133,1134],{"class":242}," [QgsProcessing.TypeVectorPolygon]\n",[233,1136,1137],{"class":121,"line":444},[233,1138,282],{"emptyLinePlaceholder":281},[233,1140,1141,1143,1145,1147,1149,1151],{"class":121,"line":459},[233,1142,369],{"class":238},[233,1144,484],{"class":291},[233,1146,487],{"class":242},[233,1148,490],{"class":238},[233,1150,493],{"class":306},[233,1152,300],{"class":242},[233,1154,1155,1157],{"class":121,"line":474},[233,1156,501],{"class":306},[233,1158,1159],{"class":242},".addParameter(QgsProcessingParameterDistance(\n",[233,1161,1162,1164,1166,1169,1171,1174,1176,1178,1180,1183,1185,1188,1190,1192],{"class":121,"line":479},[233,1163,510],{"class":306},[233,1165,513],{"class":242},[233,1167,1168],{"class":306},"TOLERANCE",[233,1170,342],{"class":242},[233,1172,1173],{"class":313},"\"Precision\"",[233,1175,342],{"class":242},[233,1177,600],{"class":526},[233,1179,490],{"class":238},[233,1181,1182],{"class":306},"1.0",[233,1184,342],{"class":242},[233,1186,1187],{"class":526},"parentParameterName",[233,1189,490],{"class":238},[233,1191,532],{"class":313},[233,1193,535],{"class":242},[233,1195,1196,1199,1201,1204],{"class":121,"line":498},[233,1197,1198],{"class":526},"            minValue",[233,1200,490],{"class":238},[233,1202,1203],{"class":306},"0.001",[233,1205,557],{"class":242},[233,1207,1208],{"class":121,"line":507},[233,1209,282],{"emptyLinePlaceholder":281},[233,1211,1212,1214,1217],{"class":121,"line":538},[233,1213,369],{"class":238},[233,1215,1216],{"class":291}," outputWkbType",[233,1218,1219],{"class":242},"(self, input_wkb_type):\n",[233,1221,1222,1224],{"class":121,"line":560},[233,1223,676],{"class":238},[233,1225,1226],{"class":242}," QgsWkbTypes.Point\n",[233,1228,1229],{"class":121,"line":568},[233,1230,282],{"emptyLinePlaceholder":281},[233,1232,1233,1235,1238],{"class":121,"line":609},[233,1234,369],{"class":238},[233,1236,1237],{"class":291}," outputFields",[233,1239,1240],{"class":242},"(self, input_fields):\n",[233,1242,1243,1246,1248],{"class":121,"line":614},[233,1244,1245],{"class":242},"        fields ",[233,1247,490],{"class":238},[233,1249,1250],{"class":242}," QgsFields(input_fields)\n",[233,1252,1253,1256,1259],{"class":121,"line":625},[233,1254,1255],{"class":242},"        fields.append(QgsField(",[233,1257,1258],{"class":313},"\"inner_distance\"",[233,1260,1261],{"class":242},", QMetaType.Type.Double))\n",[233,1263,1264,1266],{"class":121,"line":650},[233,1265,676],{"class":238},[233,1267,1268],{"class":242}," fields\n",[233,1270,1271],{"class":121,"line":673},[233,1272,282],{"emptyLinePlaceholder":281},[233,1274,1275,1277,1279],{"class":121,"line":682},[233,1276,369],{"class":238},[233,1278,619],{"class":291},[233,1280,622],{"class":242},[233,1282,1283,1285,1288,1290,1292,1295,1297,1299,1301],{"class":121,"line":687},[233,1284,501],{"class":306},[233,1286,1287],{"class":242},".tolerance ",[233,1289,490],{"class":238},[233,1291,635],{"class":306},[233,1293,1294],{"class":242},".parameterAsDouble(parameters, ",[233,1296,590],{"class":306},[233,1298,513],{"class":242},[233,1300,1168],{"class":306},[233,1302,647],{"class":242},[233,1304,1305,1307],{"class":121,"line":698},[233,1306,676],{"class":238},[233,1308,679],{"class":306},[233,1310,1311],{"class":121,"line":715},[233,1312,282],{"emptyLinePlaceholder":281},[233,1314,1315,1317,1319],{"class":121,"line":726},[233,1316,369],{"class":238},[233,1318,692],{"class":291},[233,1320,695],{"class":242},[233,1322,1323,1326,1328],{"class":121,"line":746},[233,1324,1325],{"class":242},"        geom ",[233,1327,490],{"class":238},[233,1329,1330],{"class":242}," feature.geometry()\n",[233,1332,1333,1336],{"class":121,"line":752},[233,1334,1335],{"class":238},"        if",[233,1337,1338],{"class":242}," geom.isEmpty():\n",[233,1340,1341,1344,1347,1350,1353,1356,1359,1362,1364,1367],{"class":121,"line":784},[233,1342,1343],{"class":242},"            feedback.reportError(",[233,1345,1346],{"class":238},"f",[233,1348,1349],{"class":313},"\"feature ",[233,1351,1352],{"class":306},"{",[233,1354,1355],{"class":242},"feature.id()",[233,1357,1358],{"class":306},"}",[233,1360,1361],{"class":313}," has no geometry\"",[233,1363,342],{"class":242},[233,1365,1366],{"class":306},"False",[233,1368,275],{"class":242},[233,1370,1371,1374],{"class":121,"line":799},[233,1372,1373],{"class":238},"            return",[233,1375,1376],{"class":242}," []\n",[233,1378,1379,1382,1384,1387,1389],{"class":121,"line":810},[233,1380,1381],{"class":242},"        point ",[233,1383,490],{"class":238},[233,1385,1386],{"class":242}," geom.poleOfInaccessibility(",[233,1388,590],{"class":306},[233,1390,1391],{"class":242},".tolerance)\n",[233,1393,1395,1398,1400],{"class":121,"line":1394},41,[233,1396,1397],{"class":242},"        label_point, distance ",[233,1399,490],{"class":238},[233,1401,1402],{"class":242}," point\n",[233,1404,1406],{"class":121,"line":1405},42,[233,1407,1408],{"class":242},"        feature.setGeometry(label_point)\n",[233,1410,1412,1415,1417],{"class":121,"line":1411},43,[233,1413,1414],{"class":242},"        feature.setAttributes(feature.attributes() ",[233,1416,771],{"class":238},[233,1418,1419],{"class":242}," [distance])\n",[233,1421,1423,1425],{"class":121,"line":1422},44,[233,1424,676],{"class":238},[233,1426,815],{"class":242},[14,1428,1429,821,1431,1433,1434,1436,1437,1439,1440,1443,1444,1446,1447,1450,1451,1453],{},[201,1430,820],{},[18,1432,920],{}," receives the input type and returns the output type; returning a fixed ",[18,1435,957],{}," is right here because every polygon becomes one point. ",[18,1438,930],{}," receives the input fields and returns the output fields; appending keeps the original attributes in order. The pole of inaccessibility is the point inside a polygon farthest from its boundary — a better label anchor than the centroid, which can fall outside a crescent-shaped polygon — and the method returns both the point and that distance, which is stored as a handy \"how much room for a label\" measure. Returning an empty list drops a feature from the output; ",[18,1441,1442],{},"reportError"," with ",[18,1445,1366],{}," logs it without stopping the run. Because ",[18,1448,1449],{},"QgsProcessingParameterDistance"," links to ",[18,1452,105],{},", the dialog shows the layer's units beside the value.",[190,1455,1457],{"id":1456},"let-users-run-it-in-place","Let users run it in place",[14,1459,1460,1461,1464],{},"Feature-based algorithms can modify an editable layer directly, through the ",[27,1462,1463],{},"Edit Features In-Place"," mode of the Processing Toolbox. Opting in is one method, which should say yes only when the output would be compatible with the layer being edited.",[224,1466,1468],{"className":226,"code":1467,"language":228,"meta":229,"style":229},"    # add to NormaliseText\n    def supportInPlaceEdit(self, layer):\n        return super().supportInPlaceEdit(layer)\n\n    # add to InteriorLabelPoints\n    def supportInPlaceEdit(self, layer):\n        return False\n",[18,1469,1470,1476,1486,1496,1500,1505,1513],{"__ignoreMap":229},[233,1471,1472],{"class":121,"line":235},[233,1473,1475],{"class":1474},"sjoCn","    # add to NormaliseText\n",[233,1477,1478,1480,1483],{"class":121,"line":246},[233,1479,369],{"class":238},[233,1481,1482],{"class":291}," supportInPlaceEdit",[233,1484,1485],{"class":242},"(self, layer):\n",[233,1487,1488,1490,1493],{"class":121,"line":260},[233,1489,676],{"class":238},[233,1491,1492],{"class":306}," super",[233,1494,1495],{"class":242},"().supportInPlaceEdit(layer)\n",[233,1497,1498],{"class":121,"line":266},[233,1499,282],{"emptyLinePlaceholder":281},[233,1501,1502],{"class":121,"line":272},[233,1503,1504],{"class":1474},"    # add to InteriorLabelPoints\n",[233,1506,1507,1509,1511],{"class":121,"line":278},[233,1508,369],{"class":238},[233,1510,1482],{"class":291},[233,1512,1485],{"class":242},[233,1514,1515,1517],{"class":121,"line":285},[233,1516,676],{"class":238},[233,1518,1519],{"class":306}," False\n",[14,1521,1522,1524,1525,1529],{},[201,1523,820],{}," The base class implementation returns true when the layer is an editable vector layer and the algorithm's output geometry type matches the layer's — which is the case for the text normaliser, so deferring to it is correct. The label point algorithm turns polygons into points, which can never be written back into a polygon layer, so it opts out explicitly. In-place runs apply the changes to the layer's edit buffer, where they can be reviewed and undone before saving — the same buffer described in ",[35,1526,1528],{"href":1527},"\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Fedit-features-with-transactions-pyqgis\u002F","editing features with transactions",". Opting out explicitly, rather than relying on the default, documents the decision for the next person who reads the class and protects against a future base-class change that relaxes the compatibility check.",[14,1531,1532],{},[43,1533,1536,1539,1542,1545,1548,1555,1561,1565,1569,1573,1576,1579,1582,1585],{"viewBox":1534,"role":46,"ariaLabel":1535,"xmlns":48},"0 0 760 236","Normal run versus in-place run of a feature-based algorithm: a normal run writes a new layer, an in-place run writes into the selected layer's edit buffer, which can be undone, and only algorithms whose output matches the layer are offered",[50,1537,1538],{},"New layer or the same layer",[54,1540,1541],{},"Left: a normal run reads the input and writes a new temporary or file output, leaving the source unchanged. Right: with Edit Features In-Place enabled in the Toolbox, the same processFeature output replaces features in the active editable layer's edit buffer, where the user can undo or save. The Toolbox only lists algorithms whose supportInPlaceEdit returns true for that layer.",[58,1543],{"x":60,"y":60,"width":61,"height":1544,"fill":63},"236",[82,1546,1547],{"x":84,"y":881,"style":86,"fill":87,"textAnchor":88},"Same processFeature, two destinations",[58,1549],{"x":92,"y":1550,"width":1551,"height":1552,"rx":96,"fill":1553,"stroke":1554,"style":134},"44","344","172","#eff3ff","#2563eb",[82,1556,1560],{"x":1557,"y":1558,"style":1559,"fill":1554,"textAnchor":88},"196","70","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","normal run",[82,1562,1564],{"x":1557,"y":1563,"style":143,"fill":80,"textAnchor":88},"102","writes a new output layer",[82,1566,1568],{"x":1557,"y":1567,"style":143,"fill":80,"textAnchor":88},"126","source unchanged",[82,1570,1572],{"x":1557,"y":1571,"style":109,"fill":98,"textAnchor":88},"186","every feature-based algorithm",[58,1574],{"x":1575,"y":1550,"width":1551,"height":1552,"rx":96,"fill":948,"stroke":949,"style":134},"392",[82,1577,29],{"x":1578,"y":1558,"style":1559,"fill":953,"textAnchor":88},"564",[82,1580,1581],{"x":1578,"y":1563,"style":143,"fill":80,"textAnchor":88},"writes into the edit buffer",[82,1583,1584],{"x":1578,"y":1567,"style":143,"fill":80,"textAnchor":88},"undo or save",[82,1586,1587],{"x":1578,"y":1571,"style":109,"fill":98,"textAnchor":88},"only if supportInPlaceEdit is true",[190,1589,1591],{"id":1590},"dynamic-parameters-per-feature","Dynamic parameters per feature",[14,1593,1594],{},"Some parameters make more sense as expressions evaluated per feature — a buffer distance from an attribute, a simplification tolerance that depends on area. Declaring a parameter as dynamic lets users switch it to a data-defined value in the dialog.",[224,1596,1598],{"className":226,"code":1597,"language":228,"meta":229,"style":229},"from qgis.core import QgsPropertyDefinition, QgsProcessingParameters\n\n    # replacements for the methods of the same name in InteriorLabelPoints\n    def initParameters(self, config=None):\n        param = QgsProcessingParameterDistance(\n            self.TOLERANCE, \"Precision\", defaultValue=1.0, parentParameterName=\"INPUT\")\n        param.setIsDynamic(True)\n        param.setDynamicPropertyDefinition(QgsPropertyDefinition(\n            self.TOLERANCE, \"Precision\", QgsPropertyDefinition.DoublePositive))\n        param.setDynamicLayerParameterName(\"INPUT\")\n        self.addParameter(param)\n\n    def prepareAlgorithm(self, parameters, context, feedback):\n        self.tolerance = self.parameterAsDouble(parameters, self.TOLERANCE, context)\n        self.dynamic = QgsProcessingParameters.isDynamic(parameters, self.TOLERANCE)\n        self.tolerance_property = parameters[self.TOLERANCE] if self.dynamic else None\n        return True\n\n    def processFeature(self, feature, context, feedback):\n        tolerance = self.tolerance\n        if self.dynamic:\n            expr_context = context.expressionContext()\n            expr_context.setFeature(feature)\n            tolerance, _ = self.tolerance_property.valueAsDouble(expr_context, self.tolerance)\n        point, distance = feature.geometry().poleOfInaccessibility(tolerance)\n        feature.setGeometry(point)\n        feature.setAttributes(feature.attributes() + [distance])\n        return [feature]\n",[18,1599,1600,1611,1615,1620,1634,1644,1674,1683,1688,1703,1712,1719,1723,1731,1751,1771,1805,1811,1815,1823,1835,1844,1854,1859,1875,1885,1890,1898],{"__ignoreMap":229},[233,1601,1602,1604,1606,1608],{"class":121,"line":235},[233,1603,249],{"class":238},[233,1605,252],{"class":242},[233,1607,239],{"class":238},[233,1609,1610],{"class":242}," QgsPropertyDefinition, QgsProcessingParameters\n",[233,1612,1613],{"class":121,"line":246},[233,1614,282],{"emptyLinePlaceholder":281},[233,1616,1617],{"class":121,"line":260},[233,1618,1619],{"class":1474},"    # replacements for the methods of the same name in InteriorLabelPoints\n",[233,1621,1622,1624,1626,1628,1630,1632],{"class":121,"line":266},[233,1623,369],{"class":238},[233,1625,484],{"class":291},[233,1627,487],{"class":242},[233,1629,490],{"class":238},[233,1631,493],{"class":306},[233,1633,300],{"class":242},[233,1635,1636,1639,1641],{"class":121,"line":272},[233,1637,1638],{"class":242},"        param ",[233,1640,490],{"class":238},[233,1642,1643],{"class":242}," QgsProcessingParameterDistance(\n",[233,1645,1646,1648,1650,1652,1654,1656,1658,1660,1662,1664,1666,1668,1670,1672],{"class":121,"line":278},[233,1647,510],{"class":306},[233,1649,513],{"class":242},[233,1651,1168],{"class":306},[233,1653,342],{"class":242},[233,1655,1173],{"class":313},[233,1657,342],{"class":242},[233,1659,600],{"class":526},[233,1661,490],{"class":238},[233,1663,1182],{"class":306},[233,1665,342],{"class":242},[233,1667,1187],{"class":526},[233,1669,490],{"class":238},[233,1671,532],{"class":313},[233,1673,275],{"class":242},[233,1675,1676,1679,1681],{"class":121,"line":285},[233,1677,1678],{"class":242},"        param.setIsDynamic(",[233,1680,554],{"class":306},[233,1682,275],{"class":242},[233,1684,1685],{"class":121,"line":303},[233,1686,1687],{"class":242},"        param.setDynamicPropertyDefinition(QgsPropertyDefinition(\n",[233,1689,1690,1692,1694,1696,1698,1700],{"class":121,"line":317},[233,1691,510],{"class":306},[233,1693,513],{"class":242},[233,1695,1168],{"class":306},[233,1697,342],{"class":242},[233,1699,1173],{"class":313},[233,1701,1702],{"class":242},", QgsPropertyDefinition.DoublePositive))\n",[233,1704,1705,1708,1710],{"class":121,"line":328},[233,1706,1707],{"class":242},"        param.setDynamicLayerParameterName(",[233,1709,532],{"class":313},[233,1711,275],{"class":242},[233,1713,1714,1716],{"class":121,"line":361},[233,1715,501],{"class":306},[233,1717,1718],{"class":242},".addParameter(param)\n",[233,1720,1721],{"class":121,"line":366},[233,1722,282],{"emptyLinePlaceholder":281},[233,1724,1725,1727,1729],{"class":121,"line":384},[233,1726,369],{"class":238},[233,1728,619],{"class":291},[233,1730,622],{"class":242},[233,1732,1733,1735,1737,1739,1741,1743,1745,1747,1749],{"class":121,"line":399},[233,1734,501],{"class":306},[233,1736,1287],{"class":242},[233,1738,490],{"class":238},[233,1740,635],{"class":306},[233,1742,1294],{"class":242},[233,1744,590],{"class":306},[233,1746,513],{"class":242},[233,1748,1168],{"class":306},[233,1750,647],{"class":242},[233,1752,1753,1755,1758,1760,1763,1765,1767,1769],{"class":121,"line":414},[233,1754,501],{"class":306},[233,1756,1757],{"class":242},".dynamic ",[233,1759,490],{"class":238},[233,1761,1762],{"class":242}," QgsProcessingParameters.isDynamic(parameters, ",[233,1764,590],{"class":306},[233,1766,513],{"class":242},[233,1768,1168],{"class":306},[233,1770,275],{"class":242},[233,1772,1773,1775,1778,1780,1783,1785,1787,1789,1792,1795,1797,1799,1802],{"class":121,"line":429},[233,1774,501],{"class":306},[233,1776,1777],{"class":242},".tolerance_property ",[233,1779,490],{"class":238},[233,1781,1782],{"class":242}," parameters[",[233,1784,590],{"class":306},[233,1786,513],{"class":242},[233,1788,1168],{"class":306},[233,1790,1791],{"class":242},"] ",[233,1793,1794],{"class":238},"if",[233,1796,635],{"class":306},[233,1798,1757],{"class":242},[233,1800,1801],{"class":238},"else",[233,1803,1804],{"class":306}," None\n",[233,1806,1807,1809],{"class":121,"line":444},[233,1808,676],{"class":238},[233,1810,679],{"class":306},[233,1812,1813],{"class":121,"line":459},[233,1814,282],{"emptyLinePlaceholder":281},[233,1816,1817,1819,1821],{"class":121,"line":474},[233,1818,369],{"class":238},[233,1820,692],{"class":291},[233,1822,695],{"class":242},[233,1824,1825,1828,1830,1832],{"class":121,"line":479},[233,1826,1827],{"class":242},"        tolerance ",[233,1829,490],{"class":238},[233,1831,635],{"class":306},[233,1833,1834],{"class":242},".tolerance\n",[233,1836,1837,1839,1841],{"class":121,"line":498},[233,1838,1335],{"class":238},[233,1840,635],{"class":306},[233,1842,1843],{"class":242},".dynamic:\n",[233,1845,1846,1849,1851],{"class":121,"line":507},[233,1847,1848],{"class":242},"            expr_context ",[233,1850,490],{"class":238},[233,1852,1853],{"class":242}," context.expressionContext()\n",[233,1855,1856],{"class":121,"line":538},[233,1857,1858],{"class":242},"            expr_context.setFeature(feature)\n",[233,1860,1861,1864,1866,1868,1871,1873],{"class":121,"line":560},[233,1862,1863],{"class":242},"            tolerance, _ ",[233,1865,490],{"class":238},[233,1867,635],{"class":306},[233,1869,1870],{"class":242},".tolerance_property.valueAsDouble(expr_context, ",[233,1872,590],{"class":306},[233,1874,1391],{"class":242},[233,1876,1877,1880,1882],{"class":121,"line":568},[233,1878,1879],{"class":242},"        point, distance ",[233,1881,490],{"class":238},[233,1883,1884],{"class":242}," feature.geometry().poleOfInaccessibility(tolerance)\n",[233,1886,1887],{"class":121,"line":609},[233,1888,1889],{"class":242},"        feature.setGeometry(point)\n",[233,1891,1892,1894,1896],{"class":121,"line":614},[233,1893,1414],{"class":242},[233,1895,771],{"class":238},[233,1897,1419],{"class":242},[233,1899,1900,1902],{"class":121,"line":625},[233,1901,676],{"class":238},[233,1903,815],{"class":242},[14,1905,1906,821,1908,1911,1912,1915,1916,1919],{},[201,1907,820],{},[18,1909,1910],{},"setIsDynamic"," adds the data-defined button next to the parameter in the dialog. When the user supplies an expression, the parameter value is a ",[18,1913,1914],{},"QgsProperty"," rather than a number, which ",[18,1917,1918],{},"isDynamic"," detects. Setting the feature on the context's expression context before evaluating makes field references in the expression resolve for the current feature. The static value remains the fallback when the expression returns null. Native algorithms such as buffer use exactly this pattern, which is why their distance can be driven by a field.",[190,1921,1923],{"id":1922},"qgis-version-compatibility","QGIS version compatibility",[14,1925,1926,1928,1929,1932,1933,1936,1937,1940,1941,1936,1944,1947,1948,1951],{},[18,1927,24],{}," has existed since QGIS 3.0, and ",[18,1930,1931],{},"supportInPlaceEdit"," since 3.4. Dynamic parameters arrived in 3.4. ",[18,1934,1935],{},"QgsProcessing.TypeVectorPolygon"," becomes ",[18,1938,1939],{},"Qgis.ProcessingSourceType.VectorPolygon"," from 3.36, and ",[18,1942,1943],{},"QgsWkbTypes.Point",[18,1945,1946],{},"Qgis.WkbType.Point","; the QGIS 4 series requires the scoped forms, as does ",[18,1949,1950],{},"QMetaType.Type"," for field types.",[190,1953,1955],{"id":1954},"troubleshooting","Troubleshooting",[195,1957,1958,1964,1973,1984,1995],{},[198,1959,1960,1963],{},[201,1961,1962],{},"Features are silently missing from the output."," They do not match the declared fields or geometry type.",[198,1965,1966,1969,1970,1972],{},[201,1967,1968],{},"The algorithm is not offered for in-place editing."," The layer is not editable, or ",[18,1971,1931],{}," returns false.",[198,1974,1975,1978,1979,1981,1982,513],{},[201,1976,1977],{},"Parameters are read as defaults."," They were parsed in ",[18,1980,848],{}," from stale values; parse in ",[18,1983,837],{},[198,1985,1986,821,1989,1991,1992,1994],{},[201,1987,1988],{},"Crash when running twice.",[18,1990,844],{}," returns ",[18,1993,590],{}," instead of a new instance.",[198,1996,1997,2000],{},[201,1998,1999],{},"Dynamic values ignored."," The expression context was not given the current feature.",[190,2002,2004],{"id":2003},"conclusion","Conclusion",[14,2006,2007,2008,2010,2011,2013,2014,2016,2017,828,2019,2021],{},"Use ",[18,2009,24],{}," whenever an algorithm transforms features independently. Implement ",[18,2012,848],{},", parse parameters once in ",[18,2015,837],{},", and describe any change to the output schema in ",[18,2018,930],{},[18,2020,920],{},". Opt in to in-place editing only when output matches input, and make parameters dynamic where a per-feature expression is the natural way to set them.",[190,2023,2025],{"id":2024},"frequently-asked-questions","Frequently Asked Questions",[14,2027,2028,2031],{},[201,2029,2030],{},"Can processFeature return several features?","\nYes — return a list with as many as needed, such as one per part when exploding multipart geometries.",[14,2033,2034,2037,2038,2040],{},[201,2035,2036],{},"Can I access other layers inside processFeature?","\nYes, but load them in ",[18,2039,837],{}," and keep only thread-safe objects, such as a spatial index built from a feature source.",[14,2042,2043,2050,2051,513],{},[201,2044,2045,2046,2049],{},"How is this different from the ",[18,2047,2048],{},"@alg"," decorator?","\nThe decorator targets whole-dataset scripts; the feature-based class targets per-feature transforms with in-place support. See ",[35,2052,2054],{"href":2053},"\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002Fwrite-processing-script-with-alg-decorator\u002F","writing a Processing script with @alg",[14,2056,2057,2060],{},[201,2058,2059],{},"Does it respect \"selected features only\"?","\nYes. The base class handles the input source, including selection and invalid geometry handling settings.",[190,2062,2064],{"id":2063},"related","Related",[195,2066,2067,2072,2078,2083,2089],{},[198,2068,2069,2071],{},[35,2070,38],{"href":37}," — the guide this recipe belongs to",[198,2073,2074],{},[35,2075,2077],{"href":2076},"\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002Fwrite-custom-processing-algorithm-pyqgis\u002F","Write a Custom Processing Algorithm in PyQGIS",[198,2079,2080],{},[35,2081,2082],{"href":2053},"Write a Processing Script with the @alg Decorator",[198,2084,2085],{},[35,2086,2088],{"href":2087},"\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002Fadd-parameters-to-processing-algorithm-pyqgis\u002F","Add Parameters to a Processing Algorithm",[198,2090,2091],{},[35,2092,2093],{"href":210},"Register a Processing Provider in a Plugin",[2095,2096,2097],"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 .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}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":229,"searchDepth":246,"depth":246,"links":2099},[2100,2101,2102,2103,2104,2105,2106,2107,2108,2109],{"id":192,"depth":246,"text":193},{"id":218,"depth":246,"text":219},{"id":852,"depth":246,"text":853},{"id":1456,"depth":246,"text":1457},{"id":1590,"depth":246,"text":1591},{"id":1922,"depth":246,"text":1923},{"id":1954,"depth":246,"text":1955},{"id":2003,"depth":246,"text":2004},{"id":2024,"depth":246,"text":2025},{"id":2063,"depth":246,"text":2064},"Build one-feature-in, features-out Processing algorithms with QgsProcessingFeatureBasedAlgorithm — processFeature, output fields and geometry type, extra parameters, dynamic per-feature values, and in-place editing support so the tool can modify a layer directly.","md",{"slug":2113,"type":2114,"breadcrumb":2115,"datePublished":2116,"dateModified":2116},"write-feature-based-processing-algorithm-pyqgis","article","Feature-Based Processing Algorithm","2026-09-17","\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002Fwrite-feature-based-processing-algorithm-pyqgis",{"title":5,"description":2110},"qgis-plugin-development\u002Fprocessing-provider-plugins\u002Fwrite-feature-based-processing-algorithm-pyqgis\u002Findex","1tVTWG6mraDKldlNfZRXS4rilPJ7OT6Q7ONDTszzm6M",1789632907582]