[{"data":1,"prerenderedAt":2833},["ShallowReactive",2],{"doc:\u002Fspatial-data-processing-automation\u002Fchaining-processing-algorithms":3},{"id":4,"title":5,"body":6,"description":2826,"extension":2827,"meta":2828,"navigation":301,"path":2829,"seo":2830,"stem":2831,"__hash__":2832},"docs\u002Fspatial-data-processing-automation\u002Fchaining-processing-algorithms\u002Findex.md","Chaining Processing Algorithms in PyQGIS",{"type":7,"value":8,"toc":2810},"minimark",[9,13,28,36,41,81,85,103,257,261,275,494,520,528,532,541,565,741,780,784,794,1375,1405,1409,1427,1681,1713,1717,1727,1834,1855,1859,1862,2206,2231,2235,2241,2516,2551,2555,2566,2577,2581,2639,2646,2650,2693,2697,2709,2731,2747,2769,2775,2779,2806],[10,11,5],"h1",{"id":12},"chaining-processing-algorithms-in-pyqgis",[14,15,16,17,21,22,27],"p",{},"A single call to ",[18,19,20],"code",{},"processing.run()"," solves one problem. Real geospatial work is rarely one problem: you buffer, then clip, then dissolve, then reproject, then write the result. Chaining processing algorithms is the practice of composing these discrete steps into a single, deterministic pipeline where each algorithm consumes the output of the one before it. Within the broader discipline of ",[23,24,26],"a",{"href":25},"\u002Fspatial-data-processing-automation\u002F","Spatial Data Processing & Automation with PyQGIS",", chaining is the technique that turns a toolbox of individual tools into a repeatable workflow you can version, test, and run unattended.",[14,29,30,31,35],{},"This guide focuses on the orchestration layer: how to wire algorithms together correctly, how to keep intermediate results in memory instead of littering the disk, how to share a single execution context and feedback channel across the whole chain, and how to handle the failure that ",[32,33,34],"em",{},"will"," eventually happen on step four of seven. It also covers the decision point every PyQGIS developer reaches — when a hand-written chain has outgrown its usefulness and a Graphical Model is the better tool.",[37,38,40],"h2",{"id":39},"prerequisites","Prerequisites",[42,43,44,52,59,70],"ul",{},[45,46,47,51],"li",{},[48,49,50],"strong",{},"QGIS 3.34 LTR"," (bundles Python 3.12). The patterns here also run on 3.28 LTR (Python 3.9) and the 3.40\u002F3.44 line; version-specific notes appear where the API differs.",[45,53,54,55,58],{},"The ",[18,56,57],{},"processing"," module available and the native provider registered. In the QGIS Python Console this is automatic; standalone scripts must bootstrap it (covered below).",[45,60,61,62,66,67,69],{},"Comfort with running a single algorithm. If you have not yet, start with ",[23,63,65],{"href":64},"\u002Fspatial-data-processing-automation\u002Fbatch-processing-with-pyqgis\u002Frun-processing-algorithm-from-script\u002F","running a processing algorithm from a script",", which establishes the ",[18,68,20],{}," call this page chains together.",[45,71,72,73,76,77,80],{},"Familiarity with parameter dictionaries and the fact that each algorithm returns a ",[18,74,75],{},"dict"," whose keys are the algorithm's output parameter names (usually ",[18,78,79],{},"OUTPUT",").",[37,82,84],{"id":83},"how-a-chain-actually-flows","How a Chain Actually Flows",[14,86,87,88,90,91,94,95,98,99,102],{},"The core idea is simple but worth making explicit: ",[18,89,20],{}," returns a dictionary, and the value at ",[18,92,93],{},"result['OUTPUT']"," can be handed straight to the next algorithm's ",[18,96,97],{},"INPUT",". When you request a temporary output, that value is a ",[18,100,101],{},"QgsVectorLayer"," (or its in-memory identifier) rather than a file path — so the next step reads it from RAM with no disk round-trip. The diagram below shows the data flowing from the source layer through three temporary intermediates to a single written file.",[104,105,110,111,110,115,110,119,110,126,110,136,110,144,110,150,110,154,110,158,110,161,110,164,110,168,110,170,110,173,110,178,110,180,110,185,110,190,110,193,110,200,110,204,110,208,110,212,110,217,110,219,110,221,110,227,110,232,110,237,110,239,110,241],"svg",{"viewBox":106,"role":107,"ariaLabel":108,"xmlns":109},"0 0 760 300","img","Data-flow diagram of a chained PyQGIS pipeline","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[112,113,114],"title",{},"Chained processing pipeline data flow",[116,117,118],"desc",{},"A source layer flows through buffer, clip, and dissolve algorithms as in-memory intermediates, then is written to a GeoPackage. A shared context and feedback object spans all steps.",[120,121],"rect",{"x":122,"y":122,"width":123,"height":124,"fill":125},"0","760","300","#f6f3ea",[120,127],{"x":128,"y":129,"width":130,"height":131,"rx":132,"fill":133,"stroke":134,"style":135},"20","120","110","60","6","#fffdf7","#0f766e","stroke-width:2.5",[137,138,143],"text",{"x":139,"y":140,"fill":141,"style":142},"75","146","#17211d","text-anchor:middle;font-family:sans-serif;font-size:13px;font-weight:bold","Source",[137,145,149],{"x":139,"y":146,"fill":147,"style":148},"164","#2f3b35","text-anchor:middle;font-family:sans-serif;font-size:11px","layer.gpkg",[120,151],{"x":152,"y":129,"width":130,"height":131,"rx":132,"fill":133,"stroke":153,"style":135},"170","#2563eb",[137,155,157],{"x":156,"y":140,"fill":141,"style":142},"225","native:buffer",[137,159,160],{"x":156,"y":146,"fill":147,"style":148},"TEMP → mem",[120,162],{"x":163,"y":129,"width":130,"height":131,"rx":132,"fill":133,"stroke":153,"style":135},"320",[137,165,167],{"x":166,"y":140,"fill":141,"style":142},"375","native:clip",[137,169,160],{"x":166,"y":146,"fill":147,"style":148},[120,171],{"x":172,"y":129,"width":129,"height":131,"rx":132,"fill":133,"stroke":153,"style":135},"470",[137,174,177],{"x":175,"y":140,"fill":141,"style":176},"530","text-anchor:middle;font-family:sans-serif;font-size:12px;font-weight:bold","native:dissolve",[137,179,160],{"x":175,"y":146,"fill":147,"style":148},[120,181],{"x":182,"y":129,"width":130,"height":131,"rx":132,"fill":183,"stroke":184,"style":135},"630","#26322d","#15803d",[137,186,189],{"x":187,"y":140,"fill":188,"style":142},"685","#d9f99d","Write",[137,191,192],{"x":187,"y":146,"fill":188,"style":148},"result.gpkg",[194,195],"line",{"x1":196,"y1":197,"x2":198,"y2":197,"stroke":147,"style":199},"130","150","168","stroke-width:2.5;marker-end:url(#a)",[194,201],{"x1":202,"y1":197,"x2":203,"y2":197,"stroke":147,"style":199},"280","318",[194,205],{"x1":206,"y1":197,"x2":207,"y2":197,"stroke":147,"style":199},"430","468",[194,209],{"x1":210,"y1":197,"x2":211,"y2":197,"stroke":147,"style":199},"590","628",[137,213,79],{"x":156,"y":214,"fill":215,"style":216},"112","#b45309","text-anchor:middle;font-family:sans-serif;font-size:10px",[137,218,79],{"x":166,"y":214,"fill":215,"style":216},[137,220,79],{"x":175,"y":214,"fill":215,"style":216},[120,222],{"x":152,"y":223,"width":224,"height":225,"rx":132,"fill":133,"stroke":215,"style":226},"230","420","44","stroke-width:2.5;stroke-dasharray:6 4",[137,228,231],{"x":229,"y":230,"fill":141,"style":176},"380","257","shared QgsProcessingContext + QgsProcessingFeedback",[194,233],{"x1":156,"y1":234,"x2":156,"y2":235,"stroke":215,"style":236},"180","228","stroke-width:2;stroke-dasharray:4 3",[194,238],{"x1":166,"y1":234,"x2":166,"y2":235,"stroke":215,"style":236},[194,240],{"x1":175,"y1":234,"x2":175,"y2":235,"stroke":215,"style":236},[242,243,244,245,110],"defs",{},"\n    ",[246,247,252,253,244],"marker",{"id":23,"markerWidth":248,"markerHeight":248,"refX":249,"refY":250,"orient":251},"9","7","4","auto","\n      ",[254,255],"path",{"d":256,"fill":147},"M0,0 L8,4 L0,8 Z",[37,258,260],{"id":259},"passing-output-forward-with-temporary-intermediates","Passing OUTPUT Forward with Temporary Intermediates",[14,262,263,264,266,267,270,271,274],{},"The most direct way to chain two algorithms is to capture the first result and reference its ",[18,265,79],{}," in the second call. Requesting ",[18,268,269],{},"'TEMPORARY_OUTPUT'"," (or the equivalent ",[18,272,273],{},"QgsProcessing.TEMPORARY_OUTPUT"," sentinel) tells QGIS to manage the intermediate itself — no filename, no cleanup obligation.",[276,277,282],"pre",{"className":278,"code":279,"language":280,"meta":281,"style":281},"language-python shiki shiki-themes github-dark","import processing\n\n# Step 1: buffer the source by 250 metres into a managed temporary layer\nbuffered = processing.run(\"native:buffer\", {\n    \"INPUT\": \"\u002Fdata\u002Fparcels.gpkg\",\n    \"DISTANCE\": 250,\n    \"SEGMENTS\": 8,\n    \"DISSOLVE\": False,\n    \"OUTPUT\": \"TEMPORARY_OUTPUT\",\n})\n\n# Step 2: feed the buffer OUTPUT straight into clip as INPUT\nclipped = processing.run(\"native:clip\", {\n    \"INPUT\": buffered[\"OUTPUT\"],\n    \"OVERLAY\": \"\u002Fdata\u002Fstudy_area.gpkg\",\n    \"OUTPUT\": \"TEMPORARY_OUTPUT\",\n})\n\nprint(clipped[\"OUTPUT\"])  # a QgsVectorLayer ready for the next step\n","python","",[18,283,284,296,303,310,329,344,358,371,384,397,403,408,414,429,443,456,467,472,477],{"__ignoreMap":281},[285,286,288,292],"span",{"class":194,"line":287},1,[285,289,291],{"class":290},"snl16","import",[285,293,295],{"class":294},"s95oV"," processing\n",[285,297,299],{"class":194,"line":298},2,[285,300,302],{"emptyLinePlaceholder":301},true,"\n",[285,304,306],{"class":194,"line":305},3,[285,307,309],{"class":308},"sAwPA","# Step 1: buffer the source by 250 metres into a managed temporary layer\n",[285,311,313,316,319,322,326],{"class":194,"line":312},4,[285,314,315],{"class":294},"buffered ",[285,317,318],{"class":290},"=",[285,320,321],{"class":294}," processing.run(",[285,323,325],{"class":324},"sU2Wk","\"native:buffer\"",[285,327,328],{"class":294},", {\n",[285,330,332,335,338,341],{"class":194,"line":331},5,[285,333,334],{"class":324},"    \"INPUT\"",[285,336,337],{"class":294},": ",[285,339,340],{"class":324},"\"\u002Fdata\u002Fparcels.gpkg\"",[285,342,343],{"class":294},",\n",[285,345,347,350,352,356],{"class":194,"line":346},6,[285,348,349],{"class":324},"    \"DISTANCE\"",[285,351,337],{"class":294},[285,353,355],{"class":354},"sDLfK","250",[285,357,343],{"class":294},[285,359,361,364,366,369],{"class":194,"line":360},7,[285,362,363],{"class":324},"    \"SEGMENTS\"",[285,365,337],{"class":294},[285,367,368],{"class":354},"8",[285,370,343],{"class":294},[285,372,374,377,379,382],{"class":194,"line":373},8,[285,375,376],{"class":324},"    \"DISSOLVE\"",[285,378,337],{"class":294},[285,380,381],{"class":354},"False",[285,383,343],{"class":294},[285,385,387,390,392,395],{"class":194,"line":386},9,[285,388,389],{"class":324},"    \"OUTPUT\"",[285,391,337],{"class":294},[285,393,394],{"class":324},"\"TEMPORARY_OUTPUT\"",[285,396,343],{"class":294},[285,398,400],{"class":194,"line":399},10,[285,401,402],{"class":294},"})\n",[285,404,406],{"class":194,"line":405},11,[285,407,302],{"emptyLinePlaceholder":301},[285,409,411],{"class":194,"line":410},12,[285,412,413],{"class":308},"# Step 2: feed the buffer OUTPUT straight into clip as INPUT\n",[285,415,417,420,422,424,427],{"class":194,"line":416},13,[285,418,419],{"class":294},"clipped ",[285,421,318],{"class":290},[285,423,321],{"class":294},[285,425,426],{"class":324},"\"native:clip\"",[285,428,328],{"class":294},[285,430,432,434,437,440],{"class":194,"line":431},14,[285,433,334],{"class":324},[285,435,436],{"class":294},": buffered[",[285,438,439],{"class":324},"\"OUTPUT\"",[285,441,442],{"class":294},"],\n",[285,444,446,449,451,454],{"class":194,"line":445},15,[285,447,448],{"class":324},"    \"OVERLAY\"",[285,450,337],{"class":294},[285,452,453],{"class":324},"\"\u002Fdata\u002Fstudy_area.gpkg\"",[285,455,343],{"class":294},[285,457,459,461,463,465],{"class":194,"line":458},16,[285,460,389],{"class":324},[285,462,337],{"class":294},[285,464,394],{"class":324},[285,466,343],{"class":294},[285,468,470],{"class":194,"line":469},17,[285,471,402],{"class":294},[285,473,475],{"class":194,"line":474},18,[285,476,302],{"emptyLinePlaceholder":301},[285,478,480,483,486,488,491],{"class":194,"line":479},19,[285,481,482],{"class":354},"print",[285,484,485],{"class":294},"(clipped[",[285,487,439],{"class":324},[285,489,490],{"class":294},"])  ",[285,492,493],{"class":308},"# a QgsVectorLayer ready for the next step\n",[14,495,496,499,500,502,503,506,507,509,510,512,513,515,516,519],{},[48,497,498],{},"Breakdown:"," ",[18,501,20],{}," returns a dictionary keyed by the algorithm's output names. ",[18,504,505],{},"buffered[\"OUTPUT\"]"," is a ",[18,508,101],{}," object (because we asked for a temporary output), and PyQGIS accepts that object directly as the ",[18,511,97],{}," of ",[18,514,167],{},". No file is written between the two steps, so the chain is faster and leaves no orphaned temp files on disk. Only the ",[32,517,518],{},"final"," step in a real pipeline should target a permanent path.",[14,521,522,523,527],{},"For a complete, runnable two-step version of exactly this pattern with progress reporting and validation, see the focused walkthrough on ",[23,524,526],{"href":525},"\u002Fspatial-data-processing-automation\u002Fchaining-processing-algorithms\u002Fchain-buffer-and-clip-pyqgis\u002F","chaining buffer and clip in PyQGIS",".",[37,529,531],{"id":530},"memory-layers-vs-temporary_output","Memory Layers vs TEMPORARY_OUTPUT",[14,533,534,536,537,540],{},[18,535,269],{}," and ",[18,538,539],{},"'memory:'"," look interchangeable but behave differently, and choosing wrongly leads to confusing cleanup and performance characteristics.",[42,542,543,554],{},[45,544,545,549,550,553],{},[48,546,547],{},[18,548,269],{}," lets the Processing Framework decide where the intermediate lives. With a ",[18,551,552],{},"QgsProcessingContext"," whose temporary outputs are configured for memory, results stay in RAM; otherwise QGIS may spill to a temp GeoPackage. The framework tracks these and releases them with the context.",[45,555,556,560,561,564],{},[48,557,558],{},[18,559,539],{}," (optionally ",[18,562,563],{},"'memory:my_layer_name'",") forces an in-memory provider layer. It is fast and disk-free, but the layer is yours to manage — it persists until you drop the reference, which matters in long-running loops.",[276,566,568],{"className":278,"code":567,"language":280,"meta":281,"style":281},"import processing\nfrom qgis.core import QgsProcessingContext, QgsProcessingFeedback\n\ncontext = QgsProcessingContext()\nfeedback = QgsProcessingFeedback()\n\n# Force the framework to keep temporary outputs in memory for this run\ncontext.setTemporaryFolder(\"\")  # empty = prefer in-memory where supported\n\nreprojected = processing.run(\"native:reprojectlayer\", {\n    \"INPUT\": \"\u002Fdata\u002Fsensors.gpkg\",\n    \"TARGET_CRS\": \"EPSG:25832\",\n    \"OUTPUT\": \"memory:reprojected_sensors\",\n}, context=context, feedback=feedback)\n\n# The memory layer must be added to the context's project or kept referenced\nmem_layer = reprojected[\"OUTPUT\"]\nprint(mem_layer.featureCount())\n",[18,569,570,576,589,593,603,613,617,622,636,640,654,665,677,688,710,714,719,734],{"__ignoreMap":281},[285,571,572,574],{"class":194,"line":287},[285,573,291],{"class":290},[285,575,295],{"class":294},[285,577,578,581,584,586],{"class":194,"line":298},[285,579,580],{"class":290},"from",[285,582,583],{"class":294}," qgis.core ",[285,585,291],{"class":290},[285,587,588],{"class":294}," QgsProcessingContext, QgsProcessingFeedback\n",[285,590,591],{"class":194,"line":305},[285,592,302],{"emptyLinePlaceholder":301},[285,594,595,598,600],{"class":194,"line":312},[285,596,597],{"class":294},"context ",[285,599,318],{"class":290},[285,601,602],{"class":294}," QgsProcessingContext()\n",[285,604,605,608,610],{"class":194,"line":331},[285,606,607],{"class":294},"feedback ",[285,609,318],{"class":290},[285,611,612],{"class":294}," QgsProcessingFeedback()\n",[285,614,615],{"class":194,"line":346},[285,616,302],{"emptyLinePlaceholder":301},[285,618,619],{"class":194,"line":360},[285,620,621],{"class":308},"# Force the framework to keep temporary outputs in memory for this run\n",[285,623,624,627,630,633],{"class":194,"line":373},[285,625,626],{"class":294},"context.setTemporaryFolder(",[285,628,629],{"class":324},"\"\"",[285,631,632],{"class":294},")  ",[285,634,635],{"class":308},"# empty = prefer in-memory where supported\n",[285,637,638],{"class":194,"line":386},[285,639,302],{"emptyLinePlaceholder":301},[285,641,642,645,647,649,652],{"class":194,"line":399},[285,643,644],{"class":294},"reprojected ",[285,646,318],{"class":290},[285,648,321],{"class":294},[285,650,651],{"class":324},"\"native:reprojectlayer\"",[285,653,328],{"class":294},[285,655,656,658,660,663],{"class":194,"line":405},[285,657,334],{"class":324},[285,659,337],{"class":294},[285,661,662],{"class":324},"\"\u002Fdata\u002Fsensors.gpkg\"",[285,664,343],{"class":294},[285,666,667,670,672,675],{"class":194,"line":410},[285,668,669],{"class":324},"    \"TARGET_CRS\"",[285,671,337],{"class":294},[285,673,674],{"class":324},"\"EPSG:25832\"",[285,676,343],{"class":294},[285,678,679,681,683,686],{"class":194,"line":416},[285,680,389],{"class":324},[285,682,337],{"class":294},[285,684,685],{"class":324},"\"memory:reprojected_sensors\"",[285,687,343],{"class":294},[285,689,690,693,697,699,702,705,707],{"class":194,"line":431},[285,691,692],{"class":294},"}, ",[285,694,696],{"class":695},"s9osk","context",[285,698,318],{"class":290},[285,700,701],{"class":294},"context, ",[285,703,704],{"class":695},"feedback",[285,706,318],{"class":290},[285,708,709],{"class":294},"feedback)\n",[285,711,712],{"class":194,"line":445},[285,713,302],{"emptyLinePlaceholder":301},[285,715,716],{"class":194,"line":458},[285,717,718],{"class":308},"# The memory layer must be added to the context's project or kept referenced\n",[285,720,721,724,726,729,731],{"class":194,"line":469},[285,722,723],{"class":294},"mem_layer ",[285,725,318],{"class":290},[285,727,728],{"class":294}," reprojected[",[285,730,439],{"class":324},[285,732,733],{"class":294},"]\n",[285,735,736,738],{"class":194,"line":474},[285,737,482],{"class":354},[285,739,740],{"class":294},"(mem_layer.featureCount())\n",[14,742,743,745,746,748,749,751,752,755,756,759,760,763,764,766,767,769,770,773,774,776,777,527],{},[48,744,498],{}," Passing the same ",[18,747,696],{}," to every ",[18,750,20],{}," in a chain means all intermediates share one lifecycle. Using ",[18,753,754],{},"'memory:reprojected_sensors'"," produces a named in-memory layer you can inspect (",[18,757,758],{},"featureCount()",", ",[18,761,762],{},"extent()",") between steps — invaluable for debugging a chain. For pure throughput where you never inspect intermediates, prefer ",[18,765,269],{}," and let QGIS handle disposal. As a rule: use ",[18,768,539],{}," when you want to ",[32,771,772],{},"look at"," the intermediate, ",[18,775,269],{}," when you only want to ",[32,778,779],{},"pass it on",[37,781,783],{"id":782},"sharing-context-and-feedback-across-the-chain","Sharing Context and Feedback Across the Chain",[14,785,786,787,789,790,793],{},"The single biggest quality difference between a toy chain and a production one is that every step shares one ",[18,788,552],{}," and one ",[18,791,792],{},"QgsProcessingFeedback",". The context carries the destination CRS, the project reference, the layer store for intermediates, and invalid-geometry handling policy. The feedback carries progress and cancellation. Threading both through the whole chain gives you a unified progress bar, consistent CRS handling, and one place to cancel.",[276,795,797],{"className":278,"code":796,"language":280,"meta":281,"style":281},"import processing\nfrom qgis.core import (\n    QgsProcessingContext,\n    QgsProcessingFeedback,\n    QgsProcessingException,\n    QgsProject,\n    QgsCoordinateReferenceSystem,\n)\n\n\nclass ChainFeedback(QgsProcessingFeedback):\n    \"\"\"Annotates progress with the current pipeline step.\"\"\"\n\n    def __init__(self, total_steps):\n        super().__init__()\n        self.total_steps = total_steps\n        self.step = 0\n\n    def start_step(self, name):\n        self.step += 1\n        self.pushInfo(f\"[{self.step}\u002F{self.total_steps}] {name}\")\n\n\ndef build_context():\n    context = QgsProcessingContext()\n    context.setProject(QgsProject.instance())\n    context.setDestinationCrs(QgsCoordinateReferenceSystem(\"EPSG:25832\"))\n    # Skip invalid geometries instead of aborting the whole run\n    context.setInvalidGeometryCheck(\n        QgsProcessingContext.InvalidGeometryCheck.GeometryNoCheck\n    )\n    return context\n\n\ndef run_pipeline(source, boundary, out_path):\n    feedback = ChainFeedback(total_steps=3)\n    context = build_context()\n\n    feedback.start_step(\"Buffer\")\n    step1 = processing.run(\"native:buffer\", {\n        \"INPUT\": source, \"DISTANCE\": 100, \"OUTPUT\": \"TEMPORARY_OUTPUT\",\n    }, context=context, feedback=feedback)\n\n    feedback.start_step(\"Clip to boundary\")\n    step2 = processing.run(\"native:clip\", {\n        \"INPUT\": step1[\"OUTPUT\"], \"OVERLAY\": boundary, \"OUTPUT\": \"TEMPORARY_OUTPUT\",\n    }, context=context, feedback=feedback)\n\n    feedback.start_step(\"Dissolve and write\")\n    final = processing.run(\"native:dissolve\", {\n        \"INPUT\": step2[\"OUTPUT\"], \"FIELD\": [], \"OUTPUT\": out_path,\n    }, context=context, feedback=feedback)\n\n    return final[\"OUTPUT\"]\n",[18,798,799,805,816,821,826,831,836,841,846,850,854,871,876,880,891,905,918,930,934,944,957,1006,1011,1016,1028,1038,1044,1055,1061,1067,1073,1079,1088,1093,1098,1109,1130,1140,1145,1156,1170,1197,1215,1220,1230,1244,1271,1288,1293,1303,1318,1341,1358,1363],{"__ignoreMap":281},[285,800,801,803],{"class":194,"line":287},[285,802,291],{"class":290},[285,804,295],{"class":294},[285,806,807,809,811,813],{"class":194,"line":298},[285,808,580],{"class":290},[285,810,583],{"class":294},[285,812,291],{"class":290},[285,814,815],{"class":294}," (\n",[285,817,818],{"class":194,"line":305},[285,819,820],{"class":294},"    QgsProcessingContext,\n",[285,822,823],{"class":194,"line":312},[285,824,825],{"class":294},"    QgsProcessingFeedback,\n",[285,827,828],{"class":194,"line":331},[285,829,830],{"class":294},"    QgsProcessingException,\n",[285,832,833],{"class":194,"line":346},[285,834,835],{"class":294},"    QgsProject,\n",[285,837,838],{"class":194,"line":360},[285,839,840],{"class":294},"    QgsCoordinateReferenceSystem,\n",[285,842,843],{"class":194,"line":373},[285,844,845],{"class":294},")\n",[285,847,848],{"class":194,"line":386},[285,849,302],{"emptyLinePlaceholder":301},[285,851,852],{"class":194,"line":399},[285,853,302],{"emptyLinePlaceholder":301},[285,855,856,859,863,866,868],{"class":194,"line":405},[285,857,858],{"class":290},"class",[285,860,862],{"class":861},"svObZ"," ChainFeedback",[285,864,865],{"class":294},"(",[285,867,792],{"class":861},[285,869,870],{"class":294},"):\n",[285,872,873],{"class":194,"line":410},[285,874,875],{"class":324},"    \"\"\"Annotates progress with the current pipeline step.\"\"\"\n",[285,877,878],{"class":194,"line":416},[285,879,302],{"emptyLinePlaceholder":301},[285,881,882,885,888],{"class":194,"line":431},[285,883,884],{"class":290},"    def",[285,886,887],{"class":354}," __init__",[285,889,890],{"class":294},"(self, total_steps):\n",[285,892,893,896,899,902],{"class":194,"line":445},[285,894,895],{"class":354},"        super",[285,897,898],{"class":294},"().",[285,900,901],{"class":354},"__init__",[285,903,904],{"class":294},"()\n",[285,906,907,910,913,915],{"class":194,"line":458},[285,908,909],{"class":354},"        self",[285,911,912],{"class":294},".total_steps ",[285,914,318],{"class":290},[285,916,917],{"class":294}," total_steps\n",[285,919,920,922,925,927],{"class":194,"line":469},[285,921,909],{"class":354},[285,923,924],{"class":294},".step ",[285,926,318],{"class":290},[285,928,929],{"class":354}," 0\n",[285,931,932],{"class":194,"line":474},[285,933,302],{"emptyLinePlaceholder":301},[285,935,936,938,941],{"class":194,"line":479},[285,937,884],{"class":290},[285,939,940],{"class":861}," start_step",[285,942,943],{"class":294},"(self, name):\n",[285,945,947,949,951,954],{"class":194,"line":946},20,[285,948,909],{"class":354},[285,950,924],{"class":294},[285,952,953],{"class":290},"+=",[285,955,956],{"class":354}," 1\n",[285,958,960,962,965,968,971,974,977,980,983,985,988,990,993,996,999,1001,1004],{"class":194,"line":959},21,[285,961,909],{"class":354},[285,963,964],{"class":294},".pushInfo(",[285,966,967],{"class":290},"f",[285,969,970],{"class":324},"\"[",[285,972,973],{"class":354},"{self",[285,975,976],{"class":294},".step",[285,978,979],{"class":354},"}",[285,981,982],{"class":324},"\u002F",[285,984,973],{"class":354},[285,986,987],{"class":294},".total_steps",[285,989,979],{"class":354},[285,991,992],{"class":324},"] ",[285,994,995],{"class":354},"{",[285,997,998],{"class":294},"name",[285,1000,979],{"class":354},[285,1002,1003],{"class":324},"\"",[285,1005,845],{"class":294},[285,1007,1009],{"class":194,"line":1008},22,[285,1010,302],{"emptyLinePlaceholder":301},[285,1012,1014],{"class":194,"line":1013},23,[285,1015,302],{"emptyLinePlaceholder":301},[285,1017,1019,1022,1025],{"class":194,"line":1018},24,[285,1020,1021],{"class":290},"def",[285,1023,1024],{"class":861}," build_context",[285,1026,1027],{"class":294},"():\n",[285,1029,1031,1034,1036],{"class":194,"line":1030},25,[285,1032,1033],{"class":294},"    context ",[285,1035,318],{"class":290},[285,1037,602],{"class":294},[285,1039,1041],{"class":194,"line":1040},26,[285,1042,1043],{"class":294},"    context.setProject(QgsProject.instance())\n",[285,1045,1047,1050,1052],{"class":194,"line":1046},27,[285,1048,1049],{"class":294},"    context.setDestinationCrs(QgsCoordinateReferenceSystem(",[285,1051,674],{"class":324},[285,1053,1054],{"class":294},"))\n",[285,1056,1058],{"class":194,"line":1057},28,[285,1059,1060],{"class":308},"    # Skip invalid geometries instead of aborting the whole run\n",[285,1062,1064],{"class":194,"line":1063},29,[285,1065,1066],{"class":294},"    context.setInvalidGeometryCheck(\n",[285,1068,1070],{"class":194,"line":1069},30,[285,1071,1072],{"class":294},"        QgsProcessingContext.InvalidGeometryCheck.GeometryNoCheck\n",[285,1074,1076],{"class":194,"line":1075},31,[285,1077,1078],{"class":294},"    )\n",[285,1080,1082,1085],{"class":194,"line":1081},32,[285,1083,1084],{"class":290},"    return",[285,1086,1087],{"class":294}," context\n",[285,1089,1091],{"class":194,"line":1090},33,[285,1092,302],{"emptyLinePlaceholder":301},[285,1094,1096],{"class":194,"line":1095},34,[285,1097,302],{"emptyLinePlaceholder":301},[285,1099,1101,1103,1106],{"class":194,"line":1100},35,[285,1102,1021],{"class":290},[285,1104,1105],{"class":861}," run_pipeline",[285,1107,1108],{"class":294},"(source, boundary, out_path):\n",[285,1110,1112,1115,1117,1120,1123,1125,1128],{"class":194,"line":1111},36,[285,1113,1114],{"class":294},"    feedback ",[285,1116,318],{"class":290},[285,1118,1119],{"class":294}," ChainFeedback(",[285,1121,1122],{"class":695},"total_steps",[285,1124,318],{"class":290},[285,1126,1127],{"class":354},"3",[285,1129,845],{"class":294},[285,1131,1133,1135,1137],{"class":194,"line":1132},37,[285,1134,1033],{"class":294},[285,1136,318],{"class":290},[285,1138,1139],{"class":294}," build_context()\n",[285,1141,1143],{"class":194,"line":1142},38,[285,1144,302],{"emptyLinePlaceholder":301},[285,1146,1148,1151,1154],{"class":194,"line":1147},39,[285,1149,1150],{"class":294},"    feedback.start_step(",[285,1152,1153],{"class":324},"\"Buffer\"",[285,1155,845],{"class":294},[285,1157,1159,1162,1164,1166,1168],{"class":194,"line":1158},40,[285,1160,1161],{"class":294},"    step1 ",[285,1163,318],{"class":290},[285,1165,321],{"class":294},[285,1167,325],{"class":324},[285,1169,328],{"class":294},[285,1171,1173,1176,1179,1182,1184,1187,1189,1191,1193,1195],{"class":194,"line":1172},41,[285,1174,1175],{"class":324},"        \"INPUT\"",[285,1177,1178],{"class":294},": source, ",[285,1180,1181],{"class":324},"\"DISTANCE\"",[285,1183,337],{"class":294},[285,1185,1186],{"class":354},"100",[285,1188,759],{"class":294},[285,1190,439],{"class":324},[285,1192,337],{"class":294},[285,1194,394],{"class":324},[285,1196,343],{"class":294},[285,1198,1200,1203,1205,1207,1209,1211,1213],{"class":194,"line":1199},42,[285,1201,1202],{"class":294},"    }, ",[285,1204,696],{"class":695},[285,1206,318],{"class":290},[285,1208,701],{"class":294},[285,1210,704],{"class":695},[285,1212,318],{"class":290},[285,1214,709],{"class":294},[285,1216,1218],{"class":194,"line":1217},43,[285,1219,302],{"emptyLinePlaceholder":301},[285,1221,1223,1225,1228],{"class":194,"line":1222},44,[285,1224,1150],{"class":294},[285,1226,1227],{"class":324},"\"Clip to boundary\"",[285,1229,845],{"class":294},[285,1231,1233,1236,1238,1240,1242],{"class":194,"line":1232},45,[285,1234,1235],{"class":294},"    step2 ",[285,1237,318],{"class":290},[285,1239,321],{"class":294},[285,1241,426],{"class":324},[285,1243,328],{"class":294},[285,1245,1247,1249,1252,1254,1257,1260,1263,1265,1267,1269],{"class":194,"line":1246},46,[285,1248,1175],{"class":324},[285,1250,1251],{"class":294},": step1[",[285,1253,439],{"class":324},[285,1255,1256],{"class":294},"], ",[285,1258,1259],{"class":324},"\"OVERLAY\"",[285,1261,1262],{"class":294},": boundary, ",[285,1264,439],{"class":324},[285,1266,337],{"class":294},[285,1268,394],{"class":324},[285,1270,343],{"class":294},[285,1272,1274,1276,1278,1280,1282,1284,1286],{"class":194,"line":1273},47,[285,1275,1202],{"class":294},[285,1277,696],{"class":695},[285,1279,318],{"class":290},[285,1281,701],{"class":294},[285,1283,704],{"class":695},[285,1285,318],{"class":290},[285,1287,709],{"class":294},[285,1289,1291],{"class":194,"line":1290},48,[285,1292,302],{"emptyLinePlaceholder":301},[285,1294,1296,1298,1301],{"class":194,"line":1295},49,[285,1297,1150],{"class":294},[285,1299,1300],{"class":324},"\"Dissolve and write\"",[285,1302,845],{"class":294},[285,1304,1306,1309,1311,1313,1316],{"class":194,"line":1305},50,[285,1307,1308],{"class":294},"    final ",[285,1310,318],{"class":290},[285,1312,321],{"class":294},[285,1314,1315],{"class":324},"\"native:dissolve\"",[285,1317,328],{"class":294},[285,1319,1321,1323,1326,1328,1330,1333,1336,1338],{"class":194,"line":1320},51,[285,1322,1175],{"class":324},[285,1324,1325],{"class":294},": step2[",[285,1327,439],{"class":324},[285,1329,1256],{"class":294},[285,1331,1332],{"class":324},"\"FIELD\"",[285,1334,1335],{"class":294},": [], ",[285,1337,439],{"class":324},[285,1339,1340],{"class":294},": out_path,\n",[285,1342,1344,1346,1348,1350,1352,1354,1356],{"class":194,"line":1343},52,[285,1345,1202],{"class":294},[285,1347,696],{"class":695},[285,1349,318],{"class":290},[285,1351,701],{"class":294},[285,1353,704],{"class":695},[285,1355,318],{"class":290},[285,1357,709],{"class":294},[285,1359,1361],{"class":194,"line":1360},53,[285,1362,302],{"emptyLinePlaceholder":301},[285,1364,1366,1368,1371,1373],{"class":194,"line":1365},54,[285,1367,1084],{"class":290},[285,1369,1370],{"class":294}," final[",[285,1372,439],{"class":324},[285,1374,733],{"class":294},[14,1376,1377,499,1379,1382,1383,1386,1387,1390,1391,1394,1395,536,1397,1399,1400,1404],{},[48,1378,498],{},[18,1380,1381],{},"build_context()"," centralizes policy: the destination CRS is set once, so any algorithm that respects it produces consistent output; ",[18,1384,1385],{},"setInvalidGeometryCheck(...GeometryNoCheck)"," keeps a single malformed feature from killing the run. The ",[18,1388,1389],{},"ChainFeedback"," subclass logs a ",[18,1392,1393],{},"[2\u002F3] Clip to boundary"," line at each stage, so a long pipeline reports where it is. Because ",[18,1396,696],{},[18,1398,704],{}," are passed to every call, cancellation and CRS handling are uniform end to end. This is the same context\u002Ffeedback model used throughout ",[23,1401,1403],{"href":1402},"\u002Fspatial-data-processing-automation\u002Fbatch-processing-with-pyqgis\u002F","batch processing with PyQGIS"," — chaining simply reuses one pair across sequential steps instead of one pair per file.",[37,1406,1408],{"id":1407},"error-handling-and-cancellation","Error Handling and Cancellation",[14,1410,1411,1412,1414,1415,1418,1419,1422,1423,1426],{},"Each ",[18,1413,20],{}," can raise ",[18,1416,1417],{},"QgsProcessingException",". In a chain you want to know ",[32,1420,1421],{},"which"," step failed and stop cleanly without leaving half-written outputs. Wrap the chain so failures are attributable, and check ",[18,1424,1425],{},"feedback.isCanceled()"," between expensive steps so a user-requested cancel actually halts work.",[276,1428,1430],{"className":278,"code":1429,"language":280,"meta":281,"style":281},"import processing\nfrom qgis.core import QgsProcessingException\n\n\ndef safe_chain(steps, context, feedback):\n    \"\"\"Run a list of (label, alg_id, params) steps, threading OUTPUT forward.\"\"\"\n    previous = None\n    for label, alg_id, params in steps:\n        if feedback.isCanceled():\n            feedback.pushInfo(\"Cancelled before \" + label)\n            return None\n        if previous is not None:\n            params = {**params, \"INPUT\": previous}\n        try:\n            result = processing.run(alg_id, params, context=context, feedback=feedback)\n        except QgsProcessingException as exc:\n            feedback.reportError(f\"Step '{label}' ({alg_id}) failed: {exc}\", True)\n            raise\n        previous = result[\"OUTPUT\"]\n    return previous\n",[18,1431,1432,1438,1449,1453,1457,1467,1472,1482,1496,1504,1518,1525,1544,1566,1573,1595,1609,1655,1660,1674],{"__ignoreMap":281},[285,1433,1434,1436],{"class":194,"line":287},[285,1435,291],{"class":290},[285,1437,295],{"class":294},[285,1439,1440,1442,1444,1446],{"class":194,"line":298},[285,1441,580],{"class":290},[285,1443,583],{"class":294},[285,1445,291],{"class":290},[285,1447,1448],{"class":294}," QgsProcessingException\n",[285,1450,1451],{"class":194,"line":305},[285,1452,302],{"emptyLinePlaceholder":301},[285,1454,1455],{"class":194,"line":312},[285,1456,302],{"emptyLinePlaceholder":301},[285,1458,1459,1461,1464],{"class":194,"line":331},[285,1460,1021],{"class":290},[285,1462,1463],{"class":861}," safe_chain",[285,1465,1466],{"class":294},"(steps, context, feedback):\n",[285,1468,1469],{"class":194,"line":346},[285,1470,1471],{"class":324},"    \"\"\"Run a list of (label, alg_id, params) steps, threading OUTPUT forward.\"\"\"\n",[285,1473,1474,1477,1479],{"class":194,"line":360},[285,1475,1476],{"class":294},"    previous ",[285,1478,318],{"class":290},[285,1480,1481],{"class":354}," None\n",[285,1483,1484,1487,1490,1493],{"class":194,"line":373},[285,1485,1486],{"class":290},"    for",[285,1488,1489],{"class":294}," label, alg_id, params ",[285,1491,1492],{"class":290},"in",[285,1494,1495],{"class":294}," steps:\n",[285,1497,1498,1501],{"class":194,"line":386},[285,1499,1500],{"class":290},"        if",[285,1502,1503],{"class":294}," feedback.isCanceled():\n",[285,1505,1506,1509,1512,1515],{"class":194,"line":399},[285,1507,1508],{"class":294},"            feedback.pushInfo(",[285,1510,1511],{"class":324},"\"Cancelled before \"",[285,1513,1514],{"class":290}," +",[285,1516,1517],{"class":294}," label)\n",[285,1519,1520,1523],{"class":194,"line":405},[285,1521,1522],{"class":290},"            return",[285,1524,1481],{"class":354},[285,1526,1527,1529,1532,1535,1538,1541],{"class":194,"line":410},[285,1528,1500],{"class":290},[285,1530,1531],{"class":294}," previous ",[285,1533,1534],{"class":290},"is",[285,1536,1537],{"class":290}," not",[285,1539,1540],{"class":354}," None",[285,1542,1543],{"class":294},":\n",[285,1545,1546,1549,1551,1554,1557,1560,1563],{"class":194,"line":416},[285,1547,1548],{"class":294},"            params ",[285,1550,318],{"class":290},[285,1552,1553],{"class":294}," {",[285,1555,1556],{"class":290},"**",[285,1558,1559],{"class":294},"params, ",[285,1561,1562],{"class":324},"\"INPUT\"",[285,1564,1565],{"class":294},": previous}\n",[285,1567,1568,1571],{"class":194,"line":431},[285,1569,1570],{"class":290},"        try",[285,1572,1543],{"class":294},[285,1574,1575,1578,1580,1583,1585,1587,1589,1591,1593],{"class":194,"line":445},[285,1576,1577],{"class":294},"            result ",[285,1579,318],{"class":290},[285,1581,1582],{"class":294}," processing.run(alg_id, params, ",[285,1584,696],{"class":695},[285,1586,318],{"class":290},[285,1588,701],{"class":294},[285,1590,704],{"class":695},[285,1592,318],{"class":290},[285,1594,709],{"class":294},[285,1596,1597,1600,1603,1606],{"class":194,"line":458},[285,1598,1599],{"class":290},"        except",[285,1601,1602],{"class":294}," QgsProcessingException ",[285,1604,1605],{"class":290},"as",[285,1607,1608],{"class":294}," exc:\n",[285,1610,1611,1614,1616,1619,1621,1624,1626,1629,1631,1634,1636,1639,1641,1644,1646,1648,1650,1653],{"class":194,"line":469},[285,1612,1613],{"class":294},"            feedback.reportError(",[285,1615,967],{"class":290},[285,1617,1618],{"class":324},"\"Step '",[285,1620,995],{"class":354},[285,1622,1623],{"class":294},"label",[285,1625,979],{"class":354},[285,1627,1628],{"class":324},"' (",[285,1630,995],{"class":354},[285,1632,1633],{"class":294},"alg_id",[285,1635,979],{"class":354},[285,1637,1638],{"class":324},") failed: ",[285,1640,995],{"class":354},[285,1642,1643],{"class":294},"exc",[285,1645,979],{"class":354},[285,1647,1003],{"class":324},[285,1649,759],{"class":294},[285,1651,1652],{"class":354},"True",[285,1654,845],{"class":294},[285,1656,1657],{"class":194,"line":474},[285,1658,1659],{"class":290},"            raise\n",[285,1661,1662,1665,1667,1670,1672],{"class":194,"line":479},[285,1663,1664],{"class":294},"        previous ",[285,1666,318],{"class":290},[285,1668,1669],{"class":294}," result[",[285,1671,439],{"class":324},[285,1673,733],{"class":294},[285,1675,1676,1678],{"class":194,"line":946},[285,1677,1084],{"class":290},[285,1679,1680],{"class":294}," previous\n",[14,1682,1683,1685,1686,1689,1690,1693,1694,1696,1697,1699,1700,1702,1703,1705,1706,1709,1710,1712],{},[48,1684,498],{}," The ",[18,1687,1688],{},"steps"," list is data, not code, so the same runner drives any pipeline. The ",[18,1691,1692],{},"previous"," variable carries each step's ",[18,1695,79],{}," into the next step's ",[18,1698,97],{},", except for the first step which keeps its own ",[18,1701,97],{},". Catching ",[18,1704,1417],{}," and re-raising after a clear ",[18,1707,1708],{},"reportError"," means the traceback tells you the failing step by name. Checking ",[18,1711,1425],{}," at the top of the loop honours cancellation requests between steps without interrupting an algorithm mid-execution.",[37,1714,1716],{"id":1715},"standalone-execution-of-a-chain","Standalone Execution of a Chain",[14,1718,1719,1720,1722,1723,1726],{},"To run a chain outside the QGIS desktop — in a cron job, a container, or CI — you must bootstrap the application and explicitly initialize the native algorithm provider before ",[18,1721,20],{}," will resolve ",[18,1724,1725],{},"native:*"," IDs.",[276,1728,1730],{"className":278,"code":1729,"language":280,"meta":281,"style":281},"from qgis.core import QgsApplication\n\nqgs = QgsApplication([], False)\nqgs.setPrefixPath(\"\u002Fusr\", True)   # adjust to your install\nqgs.initQgis()\n\n# Register native algorithms (otherwise processing.run cannot find them)\nfrom processing.core.Processing import Processing\nProcessing.initialize()\n\nimport processing\n# ... run your chain here ...\n\nqgs.exitQgis()\n",[18,1731,1732,1743,1747,1761,1779,1784,1788,1793,1805,1810,1814,1820,1825,1829],{"__ignoreMap":281},[285,1733,1734,1736,1738,1740],{"class":194,"line":287},[285,1735,580],{"class":290},[285,1737,583],{"class":294},[285,1739,291],{"class":290},[285,1741,1742],{"class":294}," QgsApplication\n",[285,1744,1745],{"class":194,"line":298},[285,1746,302],{"emptyLinePlaceholder":301},[285,1748,1749,1752,1754,1757,1759],{"class":194,"line":305},[285,1750,1751],{"class":294},"qgs ",[285,1753,318],{"class":290},[285,1755,1756],{"class":294}," QgsApplication([], ",[285,1758,381],{"class":354},[285,1760,845],{"class":294},[285,1762,1763,1766,1769,1771,1773,1776],{"class":194,"line":312},[285,1764,1765],{"class":294},"qgs.setPrefixPath(",[285,1767,1768],{"class":324},"\"\u002Fusr\"",[285,1770,759],{"class":294},[285,1772,1652],{"class":354},[285,1774,1775],{"class":294},")   ",[285,1777,1778],{"class":308},"# adjust to your install\n",[285,1780,1781],{"class":194,"line":331},[285,1782,1783],{"class":294},"qgs.initQgis()\n",[285,1785,1786],{"class":194,"line":346},[285,1787,302],{"emptyLinePlaceholder":301},[285,1789,1790],{"class":194,"line":360},[285,1791,1792],{"class":308},"# Register native algorithms (otherwise processing.run cannot find them)\n",[285,1794,1795,1797,1800,1802],{"class":194,"line":373},[285,1796,580],{"class":290},[285,1798,1799],{"class":294}," processing.core.Processing ",[285,1801,291],{"class":290},[285,1803,1804],{"class":294}," Processing\n",[285,1806,1807],{"class":194,"line":386},[285,1808,1809],{"class":294},"Processing.initialize()\n",[285,1811,1812],{"class":194,"line":399},[285,1813,302],{"emptyLinePlaceholder":301},[285,1815,1816,1818],{"class":194,"line":405},[285,1817,291],{"class":290},[285,1819,295],{"class":294},[285,1821,1822],{"class":194,"line":410},[285,1823,1824],{"class":308},"# ... run your chain here ...\n",[285,1826,1827],{"class":194,"line":416},[285,1828,302],{"emptyLinePlaceholder":301},[285,1830,1831],{"class":194,"line":431},[285,1832,1833],{"class":294},"qgs.exitQgis()\n",[14,1835,1836,1838,1839,1842,1843,1846,1847,1850,1851,527],{},[48,1837,498],{}," In the QGIS Python Console, the provider registry is already populated; standalone interpreters are not. ",[18,1840,1841],{},"Processing.initialize()"," registers the QGIS native and GDAL providers so algorithm IDs resolve. The import order matters — bootstrap ",[18,1844,1845],{},"QgsApplication",", then initialize Processing, then ",[18,1848,1849],{},"import processing",". The same bootstrap underpins headless workflows described in ",[23,1852,1854],{"href":1853},"\u002Fpyqgis-fundamentals-environment-setup\u002Fvirtual-environments-for-gis\u002Frunning-python-scripts-outside-qgis-desktop\u002F","running Python scripts outside QGIS Desktop",[37,1856,1858],{"id":1857},"inspecting-and-validating-between-steps","Inspecting and Validating Between Steps",[14,1860,1861],{},"A chain that runs without raising an exception is not the same as a chain that produced correct data. The classic silent failure is a clip whose inputs do not actually overlap: no error, an empty layer, and three more steps that dutifully process nothing. Insert lightweight assertions between steps so the chain fails loudly at the point where the data went wrong rather than at the end.",[276,1863,1865],{"className":278,"code":1864,"language":280,"meta":281,"style":281},"import processing\nfrom qgis.core import QgsProcessingContext, QgsProcessingFeedback\n\n\ndef assert_non_empty(layer, step_name, feedback):\n    count = layer.featureCount()\n    feedback.pushInfo(f\"{step_name}: {count} features\")\n    if count == 0:\n        raise ValueError(f\"{step_name} produced an empty layer; chain aborted\")\n    return layer\n\n\ndef validated_chain(source, overlay):\n    context = QgsProcessingContext()\n    feedback = QgsProcessingFeedback()\n\n    buffered = processing.run(\"native:buffer\", {\n        \"INPUT\": source, \"DISTANCE\": 200, \"OUTPUT\": \"memory:buffered\",\n    }, context=context, feedback=feedback)[\"OUTPUT\"]\n    assert_non_empty(buffered, \"buffer\", feedback)\n\n    # Cheap pre-check: do the extents even overlap before clipping?\n    overlay_layer = overlay if hasattr(overlay, \"extent\") else None\n    clipped = processing.run(\"native:clip\", {\n        \"INPUT\": buffered, \"OVERLAY\": overlay, \"OUTPUT\": \"memory:clipped\",\n    }, context=context, feedback=feedback)[\"OUTPUT\"]\n    assert_non_empty(clipped, \"clip\", feedback)\n    return clipped\n",[18,1866,1867,1873,1883,1887,1891,1901,1911,1941,1957,1982,1989,1993,1997,2007,2015,2023,2027,2040,2064,2085,2096,2100,2105,2135,2148,2169,2189,2199],{"__ignoreMap":281},[285,1868,1869,1871],{"class":194,"line":287},[285,1870,291],{"class":290},[285,1872,295],{"class":294},[285,1874,1875,1877,1879,1881],{"class":194,"line":298},[285,1876,580],{"class":290},[285,1878,583],{"class":294},[285,1880,291],{"class":290},[285,1882,588],{"class":294},[285,1884,1885],{"class":194,"line":305},[285,1886,302],{"emptyLinePlaceholder":301},[285,1888,1889],{"class":194,"line":312},[285,1890,302],{"emptyLinePlaceholder":301},[285,1892,1893,1895,1898],{"class":194,"line":331},[285,1894,1021],{"class":290},[285,1896,1897],{"class":861}," assert_non_empty",[285,1899,1900],{"class":294},"(layer, step_name, feedback):\n",[285,1902,1903,1906,1908],{"class":194,"line":346},[285,1904,1905],{"class":294},"    count ",[285,1907,318],{"class":290},[285,1909,1910],{"class":294}," layer.featureCount()\n",[285,1912,1913,1916,1918,1920,1922,1925,1927,1929,1931,1934,1936,1939],{"class":194,"line":360},[285,1914,1915],{"class":294},"    feedback.pushInfo(",[285,1917,967],{"class":290},[285,1919,1003],{"class":324},[285,1921,995],{"class":354},[285,1923,1924],{"class":294},"step_name",[285,1926,979],{"class":354},[285,1928,337],{"class":324},[285,1930,995],{"class":354},[285,1932,1933],{"class":294},"count",[285,1935,979],{"class":354},[285,1937,1938],{"class":324}," features\"",[285,1940,845],{"class":294},[285,1942,1943,1946,1949,1952,1955],{"class":194,"line":373},[285,1944,1945],{"class":290},"    if",[285,1947,1948],{"class":294}," count ",[285,1950,1951],{"class":290},"==",[285,1953,1954],{"class":354}," 0",[285,1956,1543],{"class":294},[285,1958,1959,1962,1965,1967,1969,1971,1973,1975,1977,1980],{"class":194,"line":386},[285,1960,1961],{"class":290},"        raise",[285,1963,1964],{"class":354}," ValueError",[285,1966,865],{"class":294},[285,1968,967],{"class":290},[285,1970,1003],{"class":324},[285,1972,995],{"class":354},[285,1974,1924],{"class":294},[285,1976,979],{"class":354},[285,1978,1979],{"class":324}," produced an empty layer; chain aborted\"",[285,1981,845],{"class":294},[285,1983,1984,1986],{"class":194,"line":399},[285,1985,1084],{"class":290},[285,1987,1988],{"class":294}," layer\n",[285,1990,1991],{"class":194,"line":405},[285,1992,302],{"emptyLinePlaceholder":301},[285,1994,1995],{"class":194,"line":410},[285,1996,302],{"emptyLinePlaceholder":301},[285,1998,1999,2001,2004],{"class":194,"line":416},[285,2000,1021],{"class":290},[285,2002,2003],{"class":861}," validated_chain",[285,2005,2006],{"class":294},"(source, overlay):\n",[285,2008,2009,2011,2013],{"class":194,"line":431},[285,2010,1033],{"class":294},[285,2012,318],{"class":290},[285,2014,602],{"class":294},[285,2016,2017,2019,2021],{"class":194,"line":445},[285,2018,1114],{"class":294},[285,2020,318],{"class":290},[285,2022,612],{"class":294},[285,2024,2025],{"class":194,"line":458},[285,2026,302],{"emptyLinePlaceholder":301},[285,2028,2029,2032,2034,2036,2038],{"class":194,"line":469},[285,2030,2031],{"class":294},"    buffered ",[285,2033,318],{"class":290},[285,2035,321],{"class":294},[285,2037,325],{"class":324},[285,2039,328],{"class":294},[285,2041,2042,2044,2046,2048,2050,2053,2055,2057,2059,2062],{"class":194,"line":474},[285,2043,1175],{"class":324},[285,2045,1178],{"class":294},[285,2047,1181],{"class":324},[285,2049,337],{"class":294},[285,2051,2052],{"class":354},"200",[285,2054,759],{"class":294},[285,2056,439],{"class":324},[285,2058,337],{"class":294},[285,2060,2061],{"class":324},"\"memory:buffered\"",[285,2063,343],{"class":294},[285,2065,2066,2068,2070,2072,2074,2076,2078,2081,2083],{"class":194,"line":479},[285,2067,1202],{"class":294},[285,2069,696],{"class":695},[285,2071,318],{"class":290},[285,2073,701],{"class":294},[285,2075,704],{"class":695},[285,2077,318],{"class":290},[285,2079,2080],{"class":294},"feedback)[",[285,2082,439],{"class":324},[285,2084,733],{"class":294},[285,2086,2087,2090,2093],{"class":194,"line":946},[285,2088,2089],{"class":294},"    assert_non_empty(buffered, ",[285,2091,2092],{"class":324},"\"buffer\"",[285,2094,2095],{"class":294},", feedback)\n",[285,2097,2098],{"class":194,"line":959},[285,2099,302],{"emptyLinePlaceholder":301},[285,2101,2102],{"class":194,"line":1008},[285,2103,2104],{"class":308},"    # Cheap pre-check: do the extents even overlap before clipping?\n",[285,2106,2107,2110,2112,2115,2118,2121,2124,2127,2130,2133],{"class":194,"line":1013},[285,2108,2109],{"class":294},"    overlay_layer ",[285,2111,318],{"class":290},[285,2113,2114],{"class":294}," overlay ",[285,2116,2117],{"class":290},"if",[285,2119,2120],{"class":354}," hasattr",[285,2122,2123],{"class":294},"(overlay, ",[285,2125,2126],{"class":324},"\"extent\"",[285,2128,2129],{"class":294},") ",[285,2131,2132],{"class":290},"else",[285,2134,1481],{"class":354},[285,2136,2137,2140,2142,2144,2146],{"class":194,"line":1018},[285,2138,2139],{"class":294},"    clipped ",[285,2141,318],{"class":290},[285,2143,321],{"class":294},[285,2145,426],{"class":324},[285,2147,328],{"class":294},[285,2149,2150,2152,2155,2157,2160,2162,2164,2167],{"class":194,"line":1030},[285,2151,1175],{"class":324},[285,2153,2154],{"class":294},": buffered, ",[285,2156,1259],{"class":324},[285,2158,2159],{"class":294},": overlay, ",[285,2161,439],{"class":324},[285,2163,337],{"class":294},[285,2165,2166],{"class":324},"\"memory:clipped\"",[285,2168,343],{"class":294},[285,2170,2171,2173,2175,2177,2179,2181,2183,2185,2187],{"class":194,"line":1040},[285,2172,1202],{"class":294},[285,2174,696],{"class":695},[285,2176,318],{"class":290},[285,2178,701],{"class":294},[285,2180,704],{"class":695},[285,2182,318],{"class":290},[285,2184,2080],{"class":294},[285,2186,439],{"class":324},[285,2188,733],{"class":294},[285,2190,2191,2194,2197],{"class":194,"line":1046},[285,2192,2193],{"class":294},"    assert_non_empty(clipped, ",[285,2195,2196],{"class":324},"\"clip\"",[285,2198,2095],{"class":294},[285,2200,2201,2203],{"class":194,"line":1057},[285,2202,1084],{"class":290},[285,2204,2205],{"class":294}," clipped\n",[14,2207,2208,2210,2211,536,2214,2217,2218,2221,2222,2225,2226,2230],{},[48,2209,498],{}," Naming intermediates with ",[18,2212,2213],{},"'memory:buffered'",[18,2215,2216],{},"'memory:clipped'"," makes each stage a real, inspectable layer. ",[18,2219,2220],{},"assert_non_empty"," logs the feature count and stops the chain the moment a step yields nothing, so the traceback names the offending stage. In development you can also call ",[18,2223,2224],{},"layer.extent()"," or open the memory layer in the canvas to eyeball geometry between steps. These checks cost almost nothing and turn a baffling empty final file into a precise error message. The same defensive mindset drives the validation steps in ",[23,2227,2229],{"href":2228},"\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002F","vector data manipulation"," workflows.",[37,2232,2234],{"id":2233},"mixing-providers-in-one-chain","Mixing Providers in One Chain",[14,2236,2237,2238,2240],{},"Chains are not limited to ",[18,2239,1725],{}," algorithms. You can interleave GDAL, GRASS, and SAGA steps in the same pipeline, as long as each provider is registered. This matters when one provider has an algorithm the others lack — GDAL for raster-to-vector and warping, GRASS for advanced topology, native for the everyday vector operations.",[276,2242,2244],{"className":278,"code":2243,"language":280,"meta":281,"style":281},"import processing\nfrom qgis.core import QgsProcessingContext, QgsProcessingFeedback\n\ncontext = QgsProcessingContext()\nfeedback = QgsProcessingFeedback()\n\n# 1. GDAL: polygonize a classified raster into vectors\npolygons = processing.run(\"gdal:polygonize\", {\n    \"INPUT\": \"\u002Fdata\u002Flandcover.tif\",\n    \"BAND\": 1,\n    \"FIELD\": \"class\",\n    \"OUTPUT\": \"TEMPORARY_OUTPUT\",\n}, context=context, feedback=feedback)\n\n# 2. native: dissolve adjacent polygons of the same class\ndissolved = processing.run(\"native:dissolve\", {\n    \"INPUT\": polygons[\"OUTPUT\"],\n    \"FIELD\": [\"class\"],\n    \"OUTPUT\": \"TEMPORARY_OUTPUT\",\n}, context=context, feedback=feedback)\n\n# 3. native: simplify the dissolved boundaries and write\nprocessing.run(\"native:simplifygeometries\", {\n    \"INPUT\": dissolved[\"OUTPUT\"],\n    \"METHOD\": 0,\n    \"TOLERANCE\": 5,\n    \"OUTPUT\": \"\u002Fdata\u002Flandcover_clean.gpkg\",\n}, context=context, feedback=feedback)\n",[18,2245,2246,2252,2262,2266,2274,2282,2286,2291,2305,2316,2328,2340,2350,2366,2370,2375,2388,2399,2410,2420,2436,2440,2445,2455,2466,2477,2489,2500],{"__ignoreMap":281},[285,2247,2248,2250],{"class":194,"line":287},[285,2249,291],{"class":290},[285,2251,295],{"class":294},[285,2253,2254,2256,2258,2260],{"class":194,"line":298},[285,2255,580],{"class":290},[285,2257,583],{"class":294},[285,2259,291],{"class":290},[285,2261,588],{"class":294},[285,2263,2264],{"class":194,"line":305},[285,2265,302],{"emptyLinePlaceholder":301},[285,2267,2268,2270,2272],{"class":194,"line":312},[285,2269,597],{"class":294},[285,2271,318],{"class":290},[285,2273,602],{"class":294},[285,2275,2276,2278,2280],{"class":194,"line":331},[285,2277,607],{"class":294},[285,2279,318],{"class":290},[285,2281,612],{"class":294},[285,2283,2284],{"class":194,"line":346},[285,2285,302],{"emptyLinePlaceholder":301},[285,2287,2288],{"class":194,"line":360},[285,2289,2290],{"class":308},"# 1. GDAL: polygonize a classified raster into vectors\n",[285,2292,2293,2296,2298,2300,2303],{"class":194,"line":373},[285,2294,2295],{"class":294},"polygons ",[285,2297,318],{"class":290},[285,2299,321],{"class":294},[285,2301,2302],{"class":324},"\"gdal:polygonize\"",[285,2304,328],{"class":294},[285,2306,2307,2309,2311,2314],{"class":194,"line":386},[285,2308,334],{"class":324},[285,2310,337],{"class":294},[285,2312,2313],{"class":324},"\"\u002Fdata\u002Flandcover.tif\"",[285,2315,343],{"class":294},[285,2317,2318,2321,2323,2326],{"class":194,"line":399},[285,2319,2320],{"class":324},"    \"BAND\"",[285,2322,337],{"class":294},[285,2324,2325],{"class":354},"1",[285,2327,343],{"class":294},[285,2329,2330,2333,2335,2338],{"class":194,"line":405},[285,2331,2332],{"class":324},"    \"FIELD\"",[285,2334,337],{"class":294},[285,2336,2337],{"class":324},"\"class\"",[285,2339,343],{"class":294},[285,2341,2342,2344,2346,2348],{"class":194,"line":410},[285,2343,389],{"class":324},[285,2345,337],{"class":294},[285,2347,394],{"class":324},[285,2349,343],{"class":294},[285,2351,2352,2354,2356,2358,2360,2362,2364],{"class":194,"line":416},[285,2353,692],{"class":294},[285,2355,696],{"class":695},[285,2357,318],{"class":290},[285,2359,701],{"class":294},[285,2361,704],{"class":695},[285,2363,318],{"class":290},[285,2365,709],{"class":294},[285,2367,2368],{"class":194,"line":431},[285,2369,302],{"emptyLinePlaceholder":301},[285,2371,2372],{"class":194,"line":445},[285,2373,2374],{"class":308},"# 2. native: dissolve adjacent polygons of the same class\n",[285,2376,2377,2380,2382,2384,2386],{"class":194,"line":458},[285,2378,2379],{"class":294},"dissolved ",[285,2381,318],{"class":290},[285,2383,321],{"class":294},[285,2385,1315],{"class":324},[285,2387,328],{"class":294},[285,2389,2390,2392,2395,2397],{"class":194,"line":469},[285,2391,334],{"class":324},[285,2393,2394],{"class":294},": polygons[",[285,2396,439],{"class":324},[285,2398,442],{"class":294},[285,2400,2401,2403,2406,2408],{"class":194,"line":474},[285,2402,2332],{"class":324},[285,2404,2405],{"class":294},": [",[285,2407,2337],{"class":324},[285,2409,442],{"class":294},[285,2411,2412,2414,2416,2418],{"class":194,"line":479},[285,2413,389],{"class":324},[285,2415,337],{"class":294},[285,2417,394],{"class":324},[285,2419,343],{"class":294},[285,2421,2422,2424,2426,2428,2430,2432,2434],{"class":194,"line":946},[285,2423,692],{"class":294},[285,2425,696],{"class":695},[285,2427,318],{"class":290},[285,2429,701],{"class":294},[285,2431,704],{"class":695},[285,2433,318],{"class":290},[285,2435,709],{"class":294},[285,2437,2438],{"class":194,"line":959},[285,2439,302],{"emptyLinePlaceholder":301},[285,2441,2442],{"class":194,"line":1008},[285,2443,2444],{"class":308},"# 3. native: simplify the dissolved boundaries and write\n",[285,2446,2447,2450,2453],{"class":194,"line":1013},[285,2448,2449],{"class":294},"processing.run(",[285,2451,2452],{"class":324},"\"native:simplifygeometries\"",[285,2454,328],{"class":294},[285,2456,2457,2459,2462,2464],{"class":194,"line":1018},[285,2458,334],{"class":324},[285,2460,2461],{"class":294},": dissolved[",[285,2463,439],{"class":324},[285,2465,442],{"class":294},[285,2467,2468,2471,2473,2475],{"class":194,"line":1030},[285,2469,2470],{"class":324},"    \"METHOD\"",[285,2472,337],{"class":294},[285,2474,122],{"class":354},[285,2476,343],{"class":294},[285,2478,2479,2482,2484,2487],{"class":194,"line":1040},[285,2480,2481],{"class":324},"    \"TOLERANCE\"",[285,2483,337],{"class":294},[285,2485,2486],{"class":354},"5",[285,2488,343],{"class":294},[285,2490,2491,2493,2495,2498],{"class":194,"line":1046},[285,2492,389],{"class":324},[285,2494,337],{"class":294},[285,2496,2497],{"class":324},"\"\u002Fdata\u002Flandcover_clean.gpkg\"",[285,2499,343],{"class":294},[285,2501,2502,2504,2506,2508,2510,2512,2514],{"class":194,"line":1057},[285,2503,692],{"class":294},[285,2505,696],{"class":695},[285,2507,318],{"class":290},[285,2509,701],{"class":294},[285,2511,704],{"class":695},[285,2513,318],{"class":290},[285,2515,709],{"class":294},[14,2517,2518,2520,2521,2523,2524,2526,2527,2530,2531,2533,2534,2536,2537,2540,2541,2543,2544,536,2548,2550],{},[48,2519,498],{}," The chain crosses provider boundaries seamlessly because every algorithm returns the same ",[18,2522,75],{}," contract and accepts a layer object as ",[18,2525,97],{},". ",[18,2528,2529],{},"gdal:polygonize"," converts the raster to vectors, ",[18,2532,177],{}," merges by the ",[18,2535,858],{}," field, and ",[18,2538,2539],{},"native:simplifygeometries"," reduces vertex count before writing. The only operational requirement is that GDAL is registered — in the QGIS Console it always is; standalone scripts get it from ",[18,2542,1841],{},". When a chain spans raster and vector, lean on the dedicated ",[23,2545,2547],{"href":2546},"\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002F","raster analysis workflows",[23,2549,2229],{"href":2228}," guides for the per-domain specifics of each step.",[37,2552,2554],{"id":2553},"when-to-graduate-to-a-graphical-model","When to Graduate to a Graphical Model",[14,2556,2557,2558,2561,2562,2565],{},"Hand-written chains are ideal when steps are conditional, when you compute parameters dynamically, or when the pipeline is embedded in a larger Python program. But once a chain is linear, stable, and meant to be re-run by non-programmers, a ",[48,2559,2560],{},"Graphical Model"," (the Processing modeler) is often the better home. A model is itself an algorithm: it gets a Processing ID, appears in the toolbox, exposes typed inputs, and can be called from ",[18,2563,2564],{},"processing.run(\"yourprovider:yourmodel\", {...})"," — so you can still drive it from Python.",[14,2567,2568,2569,2572,2573,2576],{},"Consider graduating when: the chain has more than five or six fixed steps; you want a GUI with validated inputs for analysts; you need the pipeline to appear in the toolbox and batch dialog; or you want the workflow shareable as a single ",[18,2570,2571],{},".model3"," file. Stay in code when: steps branch on data, you loop over many inputs (use the ",[23,2574,2575],{"href":1402},"batch processing"," pattern), or the chain is one part of a bigger application. A practical hybrid is to prototype in code, then export the stabilized chain as a model for distribution while keeping the Python driver for orchestration.",[37,2578,2580],{"id":2579},"compatibility-notes","Compatibility Notes",[2582,2583,2584,2597],"table",{},[2585,2586,2587],"thead",{},[2588,2589,2590,2594],"tr",{},[2591,2592,2593],"th",{},"QGIS \u002F Python",[2591,2595,2596],{},"Chaining behaviour",[2598,2599,2600,2617,2631],"tbody",{},[2588,2601,2602,2606],{},[2603,2604,2605],"td",{},"3.28 LTR \u002F Py 3.9",[2603,2607,2608,536,2610,2612,2613,2616],{},[18,2609,269],{},[18,2611,539],{}," both supported. Enum access via ",[18,2614,2615],{},"QgsProcessingContext.GeometryNoCheck"," (flat names) is reliable.",[2588,2618,2619,2624],{},[2603,2620,2621],{},[48,2622,2623],{},"3.34 LTR \u002F Py 3.12 (baseline)",[2603,2625,2626,2627,2630],{},"Scoped enums (",[18,2628,2629],{},"QgsProcessingContext.InvalidGeometryCheck.GeometryNoCheck",") available alongside flat names. Recommended target.",[2588,2632,2633,2636],{},[2603,2634,2635],{},"3.40 \u002F 3.44",[2603,2637,2638],{},"Same API surface; some algorithms gained extra parameters. Always re-verify parameter keys via the toolbox \"Copy as Python Command\".",[14,2640,2641,2642,2645],{},"Across all versions, never assume a parameter key — copy it from the algorithm's dialog (\"Advanced > Copy as Python Command\") to avoid silent ",[18,2643,2644],{},"Unknown parameter"," warnings that produce empty outputs.",[37,2647,2649],{"id":2648},"key-takeaways","Key Takeaways",[42,2651,2652,2663,2673,2681,2690],{},[45,2653,2654,2655,2657,2658,2660,2661,527],{},"Chaining works because ",[18,2656,20],{}," returns a dict, and ",[18,2659,93],{}," (a layer object when temporary) plugs directly into the next step's ",[18,2662,97],{},[45,2664,2665,2666,2668,2669,2672],{},"Prefer ",[18,2667,269],{}," for intermediates you only pass along; use ",[18,2670,2671],{},"'memory:named'"," when you want to inspect a stage while debugging.",[45,2674,2675,2676,789,2678,2680],{},"Share one ",[18,2677,552],{},[18,2679,792],{}," across the entire chain for consistent CRS handling, progress, and cancellation.",[45,2682,2683,2684,2686,2687,2689],{},"Catch ",[18,2685,1417],{}," per step so failures are attributable, and check ",[18,2688,1425],{}," between expensive operations.",[45,2691,2692],{},"Graduate a stable, linear, distributable chain to a Graphical Model; keep branching or embedded logic in Python.",[37,2694,2696],{"id":2695},"frequently-asked-questions","Frequently Asked Questions",[14,2698,2699,2702,2703,2705,2706,2708],{},[48,2700,2701],{},"Do I have to write intermediate layers to disk between steps?","\nNo. Requesting ",[18,2704,269],{}," keeps intermediates managed by the framework, and ",[18,2707,93],{}," returns a layer object you pass straight to the next algorithm. Only the final step needs a permanent path. Writing every intermediate to disk is slower and creates cleanup work.",[14,2710,2711,2719,2721,2722,2724,2725,2727,2728,2730],{},[48,2712,2713,2714,536,2716,2718],{},"What is the difference between ",[18,2715,539],{},[18,2717,269],{},"?",[18,2720,269],{}," delegates storage and cleanup to the Processing Framework. ",[18,2723,539],{}," creates an in-memory provider layer that you own and must keep referenced until done. Use ",[18,2726,539],{}," for intermediates you want to inspect; use ",[18,2729,269],{}," for fire-and-forward steps.",[14,2732,2733,2736,2737,2739,2740,2742,2743,1696,2745,527],{},[48,2734,2735],{},"Why does my chained algorithm say the parameter is unknown?","\nParameter keys are algorithm-specific and case-sensitive. A key valid for ",[18,2738,157],{}," may not exist on ",[18,2741,167],{},". Open the algorithm dialog, configure it once, and use \"Advanced > Copy as Python Command\" to get the exact dictionary, then thread ",[18,2744,79],{},[18,2746,97],{},[14,2748,2749,2752,2753,2755,2756,2758,2759,2762,2763,2765,2766,2768],{},[48,2750,2751],{},"Can I run a chain headlessly on a server?","\nYes. Bootstrap with ",[18,2754,1845],{},", call ",[18,2757,1841],{}," to register the native providers, then run your chain and call ",[18,2760,2761],{},"exitQgis()",". Without ",[18,2764,1841],{}," the ",[18,2767,1725],{}," IDs will not resolve in a standalone interpreter.",[14,2770,2771,2774],{},[48,2772,2773],{},"When should I convert my script into a Graphical Model?","\nWhen the chain is linear, stable, and meant to be re-used by others through the toolbox or batch dialog. Keep it in code when steps branch on data, when you loop over many inputs, or when the pipeline is embedded in a larger application.",[37,2776,2778],{"id":2777},"related","Related",[42,2780,2781,2785,2790,2795,2801],{},[45,2782,2783],{},[23,2784,26],{"href":25},[45,2786,2787],{},[23,2788,2789],{"href":1402},"Batch Processing with PyQGIS",[45,2791,2792],{},[23,2793,2794],{"href":2228},"Vector Data Manipulation in PyQGIS",[45,2796,2797],{},[23,2798,2800],{"href":2799},"\u002Fspatial-data-processing-automation\u002Fautomating-atlas-map-series\u002F","Automating Atlas Map Series in PyQGIS",[45,2802,2803],{},[23,2804,2805],{"href":525},"Chain Buffer and Clip in PyQGIS",[2807,2808,2809],"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 .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}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 .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}",{"title":281,"searchDepth":298,"depth":298,"links":2811},[2812,2813,2814,2815,2816,2817,2818,2819,2820,2821,2822,2823,2824,2825],{"id":39,"depth":298,"text":40},{"id":83,"depth":298,"text":84},{"id":259,"depth":298,"text":260},{"id":530,"depth":298,"text":531},{"id":782,"depth":298,"text":783},{"id":1407,"depth":298,"text":1408},{"id":1715,"depth":298,"text":1716},{"id":1857,"depth":298,"text":1858},{"id":2233,"depth":298,"text":2234},{"id":2553,"depth":298,"text":2554},{"id":2579,"depth":298,"text":2580},{"id":2648,"depth":298,"text":2649},{"id":2695,"depth":298,"text":2696},{"id":2777,"depth":298,"text":2778},"Build multi-step PyQGIS pipelines by chaining processing.run() calls, passing OUTPUT between algorithms, using memory layers, context, and feedback.","md",{},"\u002Fspatial-data-processing-automation\u002Fchaining-processing-algorithms",{"title":5,"description":2826},"spatial-data-processing-automation\u002Fchaining-processing-algorithms\u002Findex","lVmY20UfT5ogw6AwWyuDqm5-B01x8SL5gePm1y1s07E",1781792483476]