[{"data":1,"prerenderedAt":2003},["ShallowReactive",2],{"doc:\u002Fspatial-data-processing-automation\u002Fautomating-atlas-map-series\u002Fgenerate-atlas-pdf-pyqgis":3},{"id":4,"title":5,"body":6,"description":1991,"extension":1992,"meta":1993,"navigation":276,"path":1999,"seo":2000,"stem":2001,"__hash__":2002},"docs\u002Fspatial-data-processing-automation\u002Fautomating-atlas-map-series\u002Fgenerate-atlas-pdf-pyqgis\u002Findex.md","Generate an Atlas PDF in PyQGIS",{"type":7,"value":8,"toc":1975},"minimark",[9,13,23,31,189,192,197,234,237,241,244,477,516,520,527,768,800,804,810,1072,1108,1112,1285,1299,1303,1310,1386,1414,1418,1424,1464,1483,1493,1497,1500,1618,1622,1633,1714,1718,1725,1784,1804,1808,1826,1841,1847,1853,1859,1863,1877,1881,1897,1911,1931,1950,1954,1971],[10,11,5],"h1",{"id":12},"generate-an-atlas-pdf-in-pyqgis",[14,15,16,17,22],"p",{},"This is the concrete, end-to-end recipe: take an existing QGIS project that contains a layout with an atlas-driven map, enable the atlas from code, point it at a coverage layer, optionally filter the features, and export every page to a PDF — either one combined document or one file per feature. The script runs in the QGIS Python Console and, with a small bootstrap, headlessly on a server. It is the applied counterpart to ",[18,19,21],"a",{"href":20},"\u002Fspatial-data-processing-automation\u002Fautomating-atlas-map-series\u002F","Automating Atlas Map Series in PyQGIS",", which explains the atlas object model this recipe drives.",[14,24,25,26,30],{},"If your maps live in separate layouts rather than a single atlas, the non-atlas pattern in ",[18,27,29],{"href":28},"\u002Fspatial-data-processing-automation\u002Fautomated-map-layout-generation\u002Fexporting-multiple-qgis-layouts-to-pdf\u002F","exporting multiple QGIS layouts to PDF"," is the better fit. Use this page when one layout fans out into many pages from a coverage layer.",[32,33,38,39,38,43,38,47,38,54,38,64,38,73,38,77,38,82,38,87,38,91,38,97,38,100,38,104,38,109,38,112,38,115,38,121,38,128,38,132,38,135,38,139,38,143,38,149,38,153,38,158,38,161,38,169,38,173],"svg",{"viewBox":34,"role":35,"ariaLabel":36,"xmlns":37},"0 0 800 300","img","Three-stage PyQGIS atlas-to-PDF pipeline branching into a combined PDF and one PDF per feature","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[40,41,42],"title",{},"Atlas PDF pipeline: load, configure, export",[44,45,46],"desc",{},"Stage one loads the project and finds the layout. Stage two enables and configures the atlas — coverage layer, optional filter, and updateFeatures. Stage three builds a QgsLayoutExporter that branches on the per_feature flag: exportToPdf writes one combined multi-page PDF when the flag is False, and exportToPdfs writes one PDF per feature when it is True.",[48,49],"rect",{"x":50,"y":50,"width":51,"height":52,"fill":53},"0","800","300","#f6f3ea",[48,55],{"x":56,"y":57,"width":58,"height":59,"rx":60,"fill":61,"stroke":62,"style":63},"20","117","150","66","6","#fffdf7","#0f766e","stroke-width:2.5",[65,66,72],"text",{"x":67,"y":68,"fill":69,"style":70,"textAnchor":71},"95","145","#17211d","text-anchor:middle;font-family:sans-serif;font-size:12.5px;font-weight:bold","middle","Load project",[65,74,76],{"x":67,"y":75,"fill":69,"style":70,"textAnchor":71},"163","+ find layout",[65,78,81],{"x":67,"y":79,"fill":62,"style":80,"textAnchor":71},"103","text-anchor:middle;font-family:sans-serif;font-size:10.5px;font-weight:bold","Step 1",[48,83],{"x":84,"y":57,"width":85,"height":59,"rx":60,"fill":61,"stroke":86,"style":63},"206","196","#2563eb",[65,88,90],{"x":89,"y":68,"fill":69,"style":70,"textAnchor":71},"304","Configure atlas",[65,92,96],{"x":89,"y":93,"fill":94,"style":95,"textAnchor":71},"164","#2f3b35","text-anchor:middle;font-family:sans-serif;font-size:10px","coverage · filter · updateFeatures",[65,98,99],{"x":89,"y":79,"fill":86,"style":80,"textAnchor":71},"Step 2",[48,101],{"x":102,"y":57,"width":103,"height":59,"rx":60,"fill":61,"stroke":86,"style":63},"438","156",[65,105,108],{"x":106,"y":68,"fill":69,"style":107,"textAnchor":71},"516","text-anchor:middle;font-family:sans-serif;font-size:12px;font-weight:bold","QgsLayoutExporter",[65,110,111],{"x":106,"y":93,"fill":94,"style":95,"textAnchor":71},"per_feature flag",[65,113,114],{"x":106,"y":79,"fill":86,"style":80,"textAnchor":71},"Step 3",[48,116],{"x":117,"y":118,"width":58,"height":59,"rx":60,"fill":119,"stroke":120,"style":63},"632","40","#26322d","#15803d",[65,122,127],{"x":123,"y":124,"fill":125,"style":126,"textAnchor":71},"707","67","#d9f99d","text-anchor:middle;font-family:sans-serif;font-size:11.5px;font-weight:bold","exportToPdf",[65,129,131],{"x":123,"y":130,"fill":125,"style":95,"textAnchor":71},"85","one combined PDF",[48,133],{"x":117,"y":134,"width":58,"height":59,"rx":60,"fill":61,"stroke":120,"style":63},"194",[65,136,138],{"x":123,"y":137,"fill":69,"style":126,"textAnchor":71},"221","exportToPdfs",[65,140,142],{"x":123,"y":141,"fill":94,"style":95,"textAnchor":71},"239","one PDF per feature",[144,145],"line",{"x1":146,"y1":58,"x2":147,"y2":58,"stroke":94,"style":148},"170","204","stroke-width:2.5;marker-end:url(#af)",[144,150],{"x1":151,"y1":58,"x2":152,"y2":58,"stroke":94,"style":148},"402","436",[154,155],"path",{"d":156,"fill":157,"stroke":94,"style":148},"M594 150 L613 150 L613 73 L630 73","none",[154,159],{"d":160,"fill":157,"stroke":94,"style":148},"M594 150 L613 150 L613 227 L630 227",[65,162,168],{"x":163,"y":164,"fill":165,"style":166,"textAnchor":167},"623","63","#b45309","text-anchor:end;font-family:sans-serif;font-size:10px;font-weight:bold","end","False",[65,170,172],{"x":163,"y":171,"fill":165,"style":166,"textAnchor":167},"217","True",[174,175,176,177,38],"defs",{},"\n    ",[178,179,185,186,176],"marker",{"id":180,"markerWidth":181,"markerHeight":181,"refX":182,"refY":183,"orient":184},"af","9","7","4","auto","\n      ",[154,187],{"d":188,"fill":94},"M0,0 L8,4 L0,8 Z",[14,190,191],{},"This page walks the flow end to end: the diagram above previews the three functions you will build, from opening the project to the branch between a single combined book and one sheet per feature.",[193,194,196],"h2",{"id":195},"prerequisites","Prerequisites",[198,199,200,208,224,231],"ul",{},[201,202,203,207],"li",{},[204,205,206],"strong",{},"QGIS 3.34 LTR"," (Python 3.12) recommended; works on 3.28 LTR and the 3.40\u002F3.44 line.",[201,209,210,211,215,216,219,220,223],{},"A saved project (",[212,213,214],"code",{},".qgz"," or ",[212,217,218],{},".qgs",") containing a ",[204,221,222],{},"print layout"," with a map item set to \"controlled by atlas\".",[201,225,226,227,230],{},"A ",[204,228,229],{},"coverage layer"," loaded in that project (the layer whose features become pages).",[201,232,233],{},"Write access to an output directory.",[14,235,236],{},"The layout's atlas can already be configured in the project, or you can configure it entirely from code as shown below — the script does not assume the project file has the atlas pre-enabled.",[193,238,240],{"id":239},"step-1-load-the-project-and-find-the-layout","Step 1: Load the Project and Find the Layout",[14,242,243],{},"Read the project, then fetch the layout by name through the layout manager. Fail fast if either is missing.",[245,246,251],"pre",{"className":247,"code":248,"language":249,"meta":250,"style":250},"language-python shiki shiki-themes github-dark","from qgis.core import QgsProject\n\n\ndef open_layout(project_path: str, layout_name: str):\n    project = QgsProject.instance()\n    if not project.read(project_path):\n        raise RuntimeError(f\"Could not read project: {project_path}\")\n\n    layout = project.layoutManager().layoutByName(layout_name)\n    if layout is None:\n        available = [l.name() for l in project.layoutManager().layouts()]\n        raise ValueError(\n            f\"Layout '{layout_name}' not found. Available: {available}\"\n        )\n    return project, layout\n","python","",[212,252,253,271,278,283,308,320,332,366,371,382,399,422,433,462,468],{"__ignoreMap":250},[254,255,257,261,265,268],"span",{"class":144,"line":256},1,[254,258,260],{"class":259},"snl16","from",[254,262,264],{"class":263},"s95oV"," qgis.core ",[254,266,267],{"class":259},"import",[254,269,270],{"class":263}," QgsProject\n",[254,272,274],{"class":144,"line":273},2,[254,275,277],{"emptyLinePlaceholder":276},true,"\n",[254,279,281],{"class":144,"line":280},3,[254,282,277],{"emptyLinePlaceholder":276},[254,284,286,289,293,296,300,303,305],{"class":144,"line":285},4,[254,287,288],{"class":259},"def",[254,290,292],{"class":291},"svObZ"," open_layout",[254,294,295],{"class":263},"(project_path: ",[254,297,299],{"class":298},"sDLfK","str",[254,301,302],{"class":263},", layout_name: ",[254,304,299],{"class":298},[254,306,307],{"class":263},"):\n",[254,309,311,314,317],{"class":144,"line":310},5,[254,312,313],{"class":263},"    project ",[254,315,316],{"class":259},"=",[254,318,319],{"class":263}," QgsProject.instance()\n",[254,321,323,326,329],{"class":144,"line":322},6,[254,324,325],{"class":259},"    if",[254,327,328],{"class":259}," not",[254,330,331],{"class":263}," project.read(project_path):\n",[254,333,335,338,341,344,347,351,354,357,360,363],{"class":144,"line":334},7,[254,336,337],{"class":259},"        raise",[254,339,340],{"class":298}," RuntimeError",[254,342,343],{"class":263},"(",[254,345,346],{"class":259},"f",[254,348,350],{"class":349},"sU2Wk","\"Could not read project: ",[254,352,353],{"class":298},"{",[254,355,356],{"class":263},"project_path",[254,358,359],{"class":298},"}",[254,361,362],{"class":349},"\"",[254,364,365],{"class":263},")\n",[254,367,369],{"class":144,"line":368},8,[254,370,277],{"emptyLinePlaceholder":276},[254,372,374,377,379],{"class":144,"line":373},9,[254,375,376],{"class":263},"    layout ",[254,378,316],{"class":259},[254,380,381],{"class":263}," project.layoutManager().layoutByName(layout_name)\n",[254,383,385,387,390,393,396],{"class":144,"line":384},10,[254,386,325],{"class":259},[254,388,389],{"class":263}," layout ",[254,391,392],{"class":259},"is",[254,394,395],{"class":298}," None",[254,397,398],{"class":263},":\n",[254,400,402,405,407,410,413,416,419],{"class":144,"line":401},11,[254,403,404],{"class":263},"        available ",[254,406,316],{"class":259},[254,408,409],{"class":263}," [l.name() ",[254,411,412],{"class":259},"for",[254,414,415],{"class":263}," l ",[254,417,418],{"class":259},"in",[254,420,421],{"class":263}," project.layoutManager().layouts()]\n",[254,423,425,427,430],{"class":144,"line":424},12,[254,426,337],{"class":259},[254,428,429],{"class":298}," ValueError",[254,431,432],{"class":263},"(\n",[254,434,436,439,442,444,447,449,452,454,457,459],{"class":144,"line":435},13,[254,437,438],{"class":259},"            f",[254,440,441],{"class":349},"\"Layout '",[254,443,353],{"class":298},[254,445,446],{"class":263},"layout_name",[254,448,359],{"class":298},[254,450,451],{"class":349},"' not found. Available: ",[254,453,353],{"class":298},[254,455,456],{"class":263},"available",[254,458,359],{"class":298},[254,460,461],{"class":349},"\"\n",[254,463,465],{"class":144,"line":464},14,[254,466,467],{"class":263},"        )\n",[254,469,471,474],{"class":144,"line":470},15,[254,472,473],{"class":259},"    return",[254,475,476],{"class":263}," project, layout\n",[14,478,479,482,483,486,487,489,490,492,493,496,497,499,500,503,504,507,508,511,512,515],{},[204,480,481],{},"Breakdown:"," ",[212,484,485],{},"project.read(project_path)"," loads the ",[212,488,214],{},"\u002F",[212,491,218],{}," into the singleton ",[212,494,495],{},"QgsProject.instance()"," and returns ",[212,498,168],{}," on failure. ",[212,501,502],{},"layoutManager().layoutByName(...)"," returns the named print layout or ",[212,505,506],{},"None","; listing available names in the error makes a typo obvious immediately. Returning both ",[212,509,510],{},"project"," and ",[212,513,514],{},"layout"," lets later steps look up the coverage layer through the same project.",[193,517,519],{"id":518},"step-2-enable-the-atlas-and-set-coverage-filter","Step 2: Enable the Atlas and Set Coverage + Filter",[14,521,522,523,526],{},"Grab the layout's ",[212,524,525],{},"QgsLayoutAtlas",", enable it, bind the coverage layer, and apply an optional filter. Recompute the page list before checking the count.",[245,528,530],{"className":247,"code":529,"language":249,"meta":250,"style":250},"def configure_atlas(project, layout, coverage_name, filter_expr=None):\n    atlas = layout.atlas()\n    atlas.setEnabled(True)\n\n    coverage_layers = project.mapLayersByName(coverage_name)\n    if not coverage_layers:\n        raise ValueError(f\"Coverage layer '{coverage_name}' not in project\")\n    atlas.setCoverageLayer(coverage_layers[0])\n\n    if filter_expr:\n        atlas.setFilterFeatures(True)\n        atlas.setFilterExpression(filter_expr)\n\n    # Name pages so per-feature files sort and read cleanly\n    atlas.setPageNameExpression(\n        \"lpad(@atlas_featurenumber, 3, '0')\"\n    )\n\n    atlas.updateFeatures()\n    page_count = atlas.count()\n    if page_count == 0:\n        raise RuntimeError(\n            \"Atlas has 0 pages. Check the coverage layer and filter expression.\"\n        )\n    print(f\"Atlas ready: {page_count} pages\")\n    return atlas\n",[212,531,532,548,558,567,571,581,590,615,625,629,636,645,650,654,660,665,671,677,682,688,699,715,724,730,735,760],{"__ignoreMap":250},[254,533,534,536,539,542,544,546],{"class":144,"line":256},[254,535,288],{"class":259},[254,537,538],{"class":291}," configure_atlas",[254,540,541],{"class":263},"(project, layout, coverage_name, filter_expr",[254,543,316],{"class":259},[254,545,506],{"class":298},[254,547,307],{"class":263},[254,549,550,553,555],{"class":144,"line":273},[254,551,552],{"class":263},"    atlas ",[254,554,316],{"class":259},[254,556,557],{"class":263}," layout.atlas()\n",[254,559,560,563,565],{"class":144,"line":280},[254,561,562],{"class":263},"    atlas.setEnabled(",[254,564,172],{"class":298},[254,566,365],{"class":263},[254,568,569],{"class":144,"line":285},[254,570,277],{"emptyLinePlaceholder":276},[254,572,573,576,578],{"class":144,"line":310},[254,574,575],{"class":263},"    coverage_layers ",[254,577,316],{"class":259},[254,579,580],{"class":263}," project.mapLayersByName(coverage_name)\n",[254,582,583,585,587],{"class":144,"line":322},[254,584,325],{"class":259},[254,586,328],{"class":259},[254,588,589],{"class":263}," coverage_layers:\n",[254,591,592,594,596,598,600,603,605,608,610,613],{"class":144,"line":334},[254,593,337],{"class":259},[254,595,429],{"class":298},[254,597,343],{"class":263},[254,599,346],{"class":259},[254,601,602],{"class":349},"\"Coverage layer '",[254,604,353],{"class":298},[254,606,607],{"class":263},"coverage_name",[254,609,359],{"class":298},[254,611,612],{"class":349},"' not in project\"",[254,614,365],{"class":263},[254,616,617,620,622],{"class":144,"line":368},[254,618,619],{"class":263},"    atlas.setCoverageLayer(coverage_layers[",[254,621,50],{"class":298},[254,623,624],{"class":263},"])\n",[254,626,627],{"class":144,"line":373},[254,628,277],{"emptyLinePlaceholder":276},[254,630,631,633],{"class":144,"line":384},[254,632,325],{"class":259},[254,634,635],{"class":263}," filter_expr:\n",[254,637,638,641,643],{"class":144,"line":401},[254,639,640],{"class":263},"        atlas.setFilterFeatures(",[254,642,172],{"class":298},[254,644,365],{"class":263},[254,646,647],{"class":144,"line":424},[254,648,649],{"class":263},"        atlas.setFilterExpression(filter_expr)\n",[254,651,652],{"class":144,"line":435},[254,653,277],{"emptyLinePlaceholder":276},[254,655,656],{"class":144,"line":464},[254,657,659],{"class":658},"sjoCn","    # Name pages so per-feature files sort and read cleanly\n",[254,661,662],{"class":144,"line":470},[254,663,664],{"class":263},"    atlas.setPageNameExpression(\n",[254,666,668],{"class":144,"line":667},16,[254,669,670],{"class":349},"        \"lpad(@atlas_featurenumber, 3, '0')\"\n",[254,672,674],{"class":144,"line":673},17,[254,675,676],{"class":263},"    )\n",[254,678,680],{"class":144,"line":679},18,[254,681,277],{"emptyLinePlaceholder":276},[254,683,685],{"class":144,"line":684},19,[254,686,687],{"class":263},"    atlas.updateFeatures()\n",[254,689,691,694,696],{"class":144,"line":690},20,[254,692,693],{"class":263},"    page_count ",[254,695,316],{"class":259},[254,697,698],{"class":263}," atlas.count()\n",[254,700,702,704,707,710,713],{"class":144,"line":701},21,[254,703,325],{"class":259},[254,705,706],{"class":263}," page_count ",[254,708,709],{"class":259},"==",[254,711,712],{"class":298}," 0",[254,714,398],{"class":263},[254,716,718,720,722],{"class":144,"line":717},22,[254,719,337],{"class":259},[254,721,340],{"class":298},[254,723,432],{"class":263},[254,725,727],{"class":144,"line":726},23,[254,728,729],{"class":349},"            \"Atlas has 0 pages. Check the coverage layer and filter expression.\"\n",[254,731,733],{"class":144,"line":732},24,[254,734,467],{"class":263},[254,736,738,741,743,745,748,750,753,755,758],{"class":144,"line":737},25,[254,739,740],{"class":298},"    print",[254,742,343],{"class":263},[254,744,346],{"class":259},[254,746,747],{"class":349},"\"Atlas ready: ",[254,749,353],{"class":298},[254,751,752],{"class":263},"page_count",[254,754,359],{"class":298},[254,756,757],{"class":349}," pages\"",[254,759,365],{"class":263},[254,761,763,765],{"class":144,"line":762},26,[254,764,473],{"class":259},[254,766,767],{"class":263}," atlas\n",[14,769,770,482,772,775,776,779,780,783,784,787,788,791,792,795,796,799],{},[204,771,481],{},[212,773,774],{},"layout.atlas()"," returns the layout's single atlas; ",[212,777,778],{},"setEnabled(True)"," activates it. ",[212,781,782],{},"setCoverageLayer(...)"," binds the loaded layer whose features become pages. An optional ",[212,785,786],{},"filter_expr"," (any valid QGIS expression over coverage fields, e.g. ",[212,789,790],{},"\"region\" = 'North'",") trims the series. ",[212,793,794],{},"updateFeatures()"," is essential — it rebuilds the filtered page list so ",[212,797,798],{},"count()"," is accurate, and a zero-page result is caught before wasting time on an empty export.",[193,801,803],{"id":802},"step-3-export-every-page-to-pdf","Step 3: Export Every Page to PDF",[14,805,806,807,809],{},"Build a ",[212,808,108],{}," and call the atlas overload. Choose a single combined PDF or one PDF per feature.",[245,811,813],{"className":247,"code":812,"language":249,"meta":250,"style":250},"import os\nfrom qgis.core import QgsLayoutExporter\n\n\ndef export_atlas_pdf(layout, atlas, out_dir, base_name, per_feature=False):\n    os.makedirs(out_dir, exist_ok=True)\n    exporter = QgsLayoutExporter(layout)\n\n    settings = QgsLayoutExporter.PdfExportSettings()\n    settings.dpi = 300\n    settings.forceVectorOutput = True\n    settings.rasterizeWholeImage = False\n\n    if per_feature:\n        # One PDF per feature; filenames from pageNameExpression\n        result, error = exporter.exportToPdfs(\n            atlas, os.path.join(out_dir, base_name), settings\n        )\n    else:\n        # One combined multi-page PDF\n        result, error = exporter.exportToPdf(\n            atlas, os.path.join(out_dir, f\"{base_name}.pdf\"), settings\n        )\n\n    if result != QgsLayoutExporter.Success:\n        raise RuntimeError(f\"Atlas PDF export failed: {error}\")\n    print(f\"Export complete -> {out_dir}\")\n",[212,814,815,822,833,837,841,857,872,882,886,896,906,916,926,930,937,942,952,957,961,968,973,982,1004,1008,1012,1025,1049],{"__ignoreMap":250},[254,816,817,819],{"class":144,"line":256},[254,818,267],{"class":259},[254,820,821],{"class":263}," os\n",[254,823,824,826,828,830],{"class":144,"line":273},[254,825,260],{"class":259},[254,827,264],{"class":263},[254,829,267],{"class":259},[254,831,832],{"class":263}," QgsLayoutExporter\n",[254,834,835],{"class":144,"line":280},[254,836,277],{"emptyLinePlaceholder":276},[254,838,839],{"class":144,"line":285},[254,840,277],{"emptyLinePlaceholder":276},[254,842,843,845,848,851,853,855],{"class":144,"line":310},[254,844,288],{"class":259},[254,846,847],{"class":291}," export_atlas_pdf",[254,849,850],{"class":263},"(layout, atlas, out_dir, base_name, per_feature",[254,852,316],{"class":259},[254,854,168],{"class":298},[254,856,307],{"class":263},[254,858,859,862,866,868,870],{"class":144,"line":322},[254,860,861],{"class":263},"    os.makedirs(out_dir, ",[254,863,865],{"class":864},"s9osk","exist_ok",[254,867,316],{"class":259},[254,869,172],{"class":298},[254,871,365],{"class":263},[254,873,874,877,879],{"class":144,"line":334},[254,875,876],{"class":263},"    exporter ",[254,878,316],{"class":259},[254,880,881],{"class":263}," QgsLayoutExporter(layout)\n",[254,883,884],{"class":144,"line":368},[254,885,277],{"emptyLinePlaceholder":276},[254,887,888,891,893],{"class":144,"line":373},[254,889,890],{"class":263},"    settings ",[254,892,316],{"class":259},[254,894,895],{"class":263}," QgsLayoutExporter.PdfExportSettings()\n",[254,897,898,901,903],{"class":144,"line":384},[254,899,900],{"class":263},"    settings.dpi ",[254,902,316],{"class":259},[254,904,905],{"class":298}," 300\n",[254,907,908,911,913],{"class":144,"line":401},[254,909,910],{"class":263},"    settings.forceVectorOutput ",[254,912,316],{"class":259},[254,914,915],{"class":298}," True\n",[254,917,918,921,923],{"class":144,"line":424},[254,919,920],{"class":263},"    settings.rasterizeWholeImage ",[254,922,316],{"class":259},[254,924,925],{"class":298}," False\n",[254,927,928],{"class":144,"line":435},[254,929,277],{"emptyLinePlaceholder":276},[254,931,932,934],{"class":144,"line":464},[254,933,325],{"class":259},[254,935,936],{"class":263}," per_feature:\n",[254,938,939],{"class":144,"line":470},[254,940,941],{"class":658},"        # One PDF per feature; filenames from pageNameExpression\n",[254,943,944,947,949],{"class":144,"line":667},[254,945,946],{"class":263},"        result, error ",[254,948,316],{"class":259},[254,950,951],{"class":263}," exporter.exportToPdfs(\n",[254,953,954],{"class":144,"line":673},[254,955,956],{"class":263},"            atlas, os.path.join(out_dir, base_name), settings\n",[254,958,959],{"class":144,"line":679},[254,960,467],{"class":263},[254,962,963,966],{"class":144,"line":684},[254,964,965],{"class":259},"    else",[254,967,398],{"class":263},[254,969,970],{"class":144,"line":690},[254,971,972],{"class":658},"        # One combined multi-page PDF\n",[254,974,975,977,979],{"class":144,"line":701},[254,976,946],{"class":263},[254,978,316],{"class":259},[254,980,981],{"class":263}," exporter.exportToPdf(\n",[254,983,984,987,989,991,993,996,998,1001],{"class":144,"line":717},[254,985,986],{"class":263},"            atlas, os.path.join(out_dir, ",[254,988,346],{"class":259},[254,990,362],{"class":349},[254,992,353],{"class":298},[254,994,995],{"class":263},"base_name",[254,997,359],{"class":298},[254,999,1000],{"class":349},".pdf\"",[254,1002,1003],{"class":263},"), settings\n",[254,1005,1006],{"class":144,"line":726},[254,1007,467],{"class":263},[254,1009,1010],{"class":144,"line":732},[254,1011,277],{"emptyLinePlaceholder":276},[254,1013,1014,1016,1019,1022],{"class":144,"line":737},[254,1015,325],{"class":259},[254,1017,1018],{"class":263}," result ",[254,1020,1021],{"class":259},"!=",[254,1023,1024],{"class":263}," QgsLayoutExporter.Success:\n",[254,1026,1027,1029,1031,1033,1035,1038,1040,1043,1045,1047],{"class":144,"line":762},[254,1028,337],{"class":259},[254,1030,340],{"class":298},[254,1032,343],{"class":263},[254,1034,346],{"class":259},[254,1036,1037],{"class":349},"\"Atlas PDF export failed: ",[254,1039,353],{"class":298},[254,1041,1042],{"class":263},"error",[254,1044,359],{"class":298},[254,1046,362],{"class":349},[254,1048,365],{"class":263},[254,1050,1052,1054,1056,1058,1061,1063,1066,1068,1070],{"class":144,"line":1051},27,[254,1053,740],{"class":298},[254,1055,343],{"class":263},[254,1057,346],{"class":259},[254,1059,1060],{"class":349},"\"Export complete -> ",[254,1062,353],{"class":298},[254,1064,1065],{"class":263},"out_dir",[254,1067,359],{"class":298},[254,1069,362],{"class":349},[254,1071,365],{"class":263},[14,1073,1074,1076,1077,1080,1081,1084,1085,1088,1089,1092,1093,1096,1097,1099,1100,1103,1104,1107],{},[204,1075,481],{}," Passing ",[212,1078,1079],{},"atlas"," as the first argument is what makes the exporter iterate the series rather than render one page. ",[212,1082,1083],{},"exportToPdf(atlas, path, settings)"," produces one combined document; ",[212,1086,1087],{},"exportToPdfs(atlas, base_path, settings)"," writes a separate file per feature, named from the ",[212,1090,1091],{},"pageNameExpression"," set in step two. The atlas overloads return a ",[212,1094,1095],{},"(result, error)"," tuple — note this differs from the single-layout ",[212,1098,127],{},", which returns only a status code. ",[212,1101,1102],{},"forceVectorOutput = True"," keeps text and lines crisp; ",[212,1105,1106],{},"rasterizeWholeImage = False"," avoids flattening the whole page to a bitmap.",[193,1109,1111],{"id":1110},"step-4-run-it-all-together","Step 4: Run It All Together",[245,1113,1115],{"className":247,"code":1114,"language":249,"meta":250,"style":250},"def main():\n    project, layout = open_layout(\n        \"\u002Fdata\u002Fprojects\u002Fdistricts.qgz\",\n        layout_name=\"DistrictMaps\",\n    )\n    atlas = configure_atlas(\n        project, layout,\n        coverage_name=\"districts\",\n        filter_expr='\"status\" = \\'active\\'',\n    )\n    export_atlas_pdf(\n        layout, atlas,\n        out_dir=\"\u002Fdata\u002Fatlas_out\",\n        base_name=\"district_atlas\",\n        per_feature=False,   # True for one PDF per district\n    )\n\n\n# In the QGIS Python Console:\n# main()\n",[212,1116,1117,1127,1137,1145,1157,1161,1170,1175,1187,1210,1214,1219,1224,1236,1248,1263,1267,1271,1275,1280],{"__ignoreMap":250},[254,1118,1119,1121,1124],{"class":144,"line":256},[254,1120,288],{"class":259},[254,1122,1123],{"class":291}," main",[254,1125,1126],{"class":263},"():\n",[254,1128,1129,1132,1134],{"class":144,"line":273},[254,1130,1131],{"class":263},"    project, layout ",[254,1133,316],{"class":259},[254,1135,1136],{"class":263}," open_layout(\n",[254,1138,1139,1142],{"class":144,"line":280},[254,1140,1141],{"class":349},"        \"\u002Fdata\u002Fprojects\u002Fdistricts.qgz\"",[254,1143,1144],{"class":263},",\n",[254,1146,1147,1150,1152,1155],{"class":144,"line":285},[254,1148,1149],{"class":864},"        layout_name",[254,1151,316],{"class":259},[254,1153,1154],{"class":349},"\"DistrictMaps\"",[254,1156,1144],{"class":263},[254,1158,1159],{"class":144,"line":310},[254,1160,676],{"class":263},[254,1162,1163,1165,1167],{"class":144,"line":322},[254,1164,552],{"class":263},[254,1166,316],{"class":259},[254,1168,1169],{"class":263}," configure_atlas(\n",[254,1171,1172],{"class":144,"line":334},[254,1173,1174],{"class":263},"        project, layout,\n",[254,1176,1177,1180,1182,1185],{"class":144,"line":368},[254,1178,1179],{"class":864},"        coverage_name",[254,1181,316],{"class":259},[254,1183,1184],{"class":349},"\"districts\"",[254,1186,1144],{"class":263},[254,1188,1189,1192,1194,1197,1200,1203,1205,1208],{"class":144,"line":373},[254,1190,1191],{"class":864},"        filter_expr",[254,1193,316],{"class":259},[254,1195,1196],{"class":349},"'\"status\" = ",[254,1198,1199],{"class":298},"\\'",[254,1201,1202],{"class":349},"active",[254,1204,1199],{"class":298},[254,1206,1207],{"class":349},"'",[254,1209,1144],{"class":263},[254,1211,1212],{"class":144,"line":384},[254,1213,676],{"class":263},[254,1215,1216],{"class":144,"line":401},[254,1217,1218],{"class":263},"    export_atlas_pdf(\n",[254,1220,1221],{"class":144,"line":424},[254,1222,1223],{"class":263},"        layout, atlas,\n",[254,1225,1226,1229,1231,1234],{"class":144,"line":435},[254,1227,1228],{"class":864},"        out_dir",[254,1230,316],{"class":259},[254,1232,1233],{"class":349},"\"\u002Fdata\u002Fatlas_out\"",[254,1235,1144],{"class":263},[254,1237,1238,1241,1243,1246],{"class":144,"line":464},[254,1239,1240],{"class":864},"        base_name",[254,1242,316],{"class":259},[254,1244,1245],{"class":349},"\"district_atlas\"",[254,1247,1144],{"class":263},[254,1249,1250,1253,1255,1257,1260],{"class":144,"line":470},[254,1251,1252],{"class":864},"        per_feature",[254,1254,316],{"class":259},[254,1256,168],{"class":298},[254,1258,1259],{"class":263},",   ",[254,1261,1262],{"class":658},"# True for one PDF per district\n",[254,1264,1265],{"class":144,"line":667},[254,1266,676],{"class":263},[254,1268,1269],{"class":144,"line":673},[254,1270,277],{"emptyLinePlaceholder":276},[254,1272,1273],{"class":144,"line":679},[254,1274,277],{"emptyLinePlaceholder":276},[254,1276,1277],{"class":144,"line":684},[254,1278,1279],{"class":658},"# In the QGIS Python Console:\n",[254,1281,1282],{"class":144,"line":690},[254,1283,1284],{"class":658},"# main()\n",[14,1286,1287,1289,1290,1293,1294,511,1296,1298],{},[204,1288,481],{}," The three functions compose into a linear flow: open, configure, export. Switching ",[212,1291,1292],{},"per_feature"," between ",[212,1295,168],{},[212,1297,172],{}," is the only change needed to toggle between a single book and individual sheets. Because each function raises on its own failure mode, a problem (missing project, missing coverage layer, empty filter, export error) is reported at the exact stage it occurs.",[193,1300,1302],{"id":1301},"running-headless-standalone-script","Running Headless (Standalone Script)",[14,1304,1305,1306,1309],{},"For cron jobs, containers, or CI, bootstrap QGIS before any of the above runs. No GUI or ",[212,1307,1308],{},"iface"," is required for atlas export.",[245,1311,1313],{"className":247,"code":1312,"language":249,"meta":250,"style":250},"from qgis.core import QgsApplication\n\nqgs = QgsApplication([], False)\nqgs.setPrefixPath(\"\u002Fusr\", True)   # adjust to your install prefix\nqgs.initQgis()\n\n# main()  # call the workflow defined above\n\nqgs.exitQgis()\n",[212,1314,1315,1326,1330,1344,1363,1368,1372,1377,1381],{"__ignoreMap":250},[254,1316,1317,1319,1321,1323],{"class":144,"line":256},[254,1318,260],{"class":259},[254,1320,264],{"class":263},[254,1322,267],{"class":259},[254,1324,1325],{"class":263}," QgsApplication\n",[254,1327,1328],{"class":144,"line":273},[254,1329,277],{"emptyLinePlaceholder":276},[254,1331,1332,1335,1337,1340,1342],{"class":144,"line":280},[254,1333,1334],{"class":263},"qgs ",[254,1336,316],{"class":259},[254,1338,1339],{"class":263}," QgsApplication([], ",[254,1341,168],{"class":298},[254,1343,365],{"class":263},[254,1345,1346,1349,1352,1355,1357,1360],{"class":144,"line":285},[254,1347,1348],{"class":263},"qgs.setPrefixPath(",[254,1350,1351],{"class":349},"\"\u002Fusr\"",[254,1353,1354],{"class":263},", ",[254,1356,172],{"class":298},[254,1358,1359],{"class":263},")   ",[254,1361,1362],{"class":658},"# adjust to your install prefix\n",[254,1364,1365],{"class":144,"line":310},[254,1366,1367],{"class":263},"qgs.initQgis()\n",[254,1369,1370],{"class":144,"line":322},[254,1371,277],{"emptyLinePlaceholder":276},[254,1373,1374],{"class":144,"line":334},[254,1375,1376],{"class":658},"# main()  # call the workflow defined above\n",[254,1378,1379],{"class":144,"line":368},[254,1380,277],{"emptyLinePlaceholder":276},[254,1382,1383],{"class":144,"line":373},[254,1384,1385],{"class":263},"qgs.exitQgis()\n",[14,1387,1388,482,1390,1393,1394,1397,1398,1401,1402,1404,1405,1408,1409,1413],{},[204,1389,481],{},[212,1391,1392],{},"QgsApplication([], False)"," starts QGIS with no GUI; ",[212,1395,1396],{},"setPrefixPath"," points it at the install so providers and data paths resolve. ",[212,1399,1400],{},"initQgis()"," must run before reading projects or exporting. Atlas export depends only on the layout and project, not on a map canvas, so it works fully headless. Always pair ",[212,1403,1400],{}," with ",[212,1406,1407],{},"exitQgis()"," to flush and release resources — important for long-running batch servers. This is the same headless pattern used across ",[18,1410,1412],{"href":1411},"\u002Fspatial-data-processing-automation\u002F","Spatial Data Processing & Automation with PyQGIS"," pipelines.",[193,1415,1417],{"id":1416},"optional-per-feature-filenames-and-tuning-dpi","Optional: Per-Feature Filenames and Tuning DPI",[14,1419,1420,1421,1423],{},"When you export one PDF per feature, the filenames come straight from the atlas ",[212,1422,1091],{},". Make it produce a readable, sortable, filesystem-safe string and you get a clean folder of named sheets with no extra code.",[245,1425,1427],{"className":247,"code":1426,"language":249,"meta":250,"style":250},"# A descriptive, sortable, safe page name -> \"012_riverside_north\"\natlas.setPageNameExpression(\n    \"lpad(@atlas_featurenumber, 3, '0') || '_' || \"\n    \"lower(replace(\\\"district_name\\\", ' ', '_'))\"\n)\n",[212,1428,1429,1434,1439,1444,1460],{"__ignoreMap":250},[254,1430,1431],{"class":144,"line":256},[254,1432,1433],{"class":658},"# A descriptive, sortable, safe page name -> \"012_riverside_north\"\n",[254,1435,1436],{"class":144,"line":273},[254,1437,1438],{"class":263},"atlas.setPageNameExpression(\n",[254,1440,1441],{"class":144,"line":280},[254,1442,1443],{"class":349},"    \"lpad(@atlas_featurenumber, 3, '0') || '_' || \"\n",[254,1445,1446,1449,1452,1455,1457],{"class":144,"line":285},[254,1447,1448],{"class":349},"    \"lower(replace(",[254,1450,1451],{"class":298},"\\\"",[254,1453,1454],{"class":349},"district_name",[254,1456,1451],{"class":298},[254,1458,1459],{"class":349},", ' ', '_'))\"\n",[254,1461,1462],{"class":144,"line":310},[254,1463,365],{"class":263},[14,1465,1466,482,1468,1471,1472,1475,1476,1479,1480,1482],{},[204,1467,481],{},[212,1469,1470],{},"lpad(@atlas_featurenumber, 3, '0')"," zero-pads the page index so ",[212,1473,1474],{},"012"," sorts before ",[212,1477,1478],{},"100","; concatenating the lower-cased, space-stripped district name yields a filename that is both human-readable and safe across operating systems. Because ",[212,1481,138],{}," derives each filename from this expression, controlling the expression is the only thing needed to control output naming.",[14,1484,1485,1486,1489,1490,1492],{},"For tuning quality versus size: ",[212,1487,1488],{},"settings.dpi"," drives raster resolution (300 for print, 150 for quick previews), ",[212,1491,1102],{}," keeps vector layers sharp regardless of DPI, and for very large series consider exporting per-feature to cap individual file size and memory pressure rather than one giant document.",[193,1494,1496],{"id":1495},"how-the-atlas-decides-each-pages-extent","How the atlas decides each page's extent",[14,1498,1499],{},"The coverage layer drives the map frame, and the three extent modes produce very different series from the same features.",[14,1501,1502],{},[32,1503,1506,1509,1512,1516,1522,1565,1591],{"viewBox":1504,"role":35,"ariaLabel":1505,"xmlns":37},"0 0 760 244","Three atlas extent modes applied to the same coverage feature: margin around the feature, a predefined scale, and a fixed scale, showing how the framing differs",[40,1507,1508],{},"Three ways the atlas frames each page",[44,1510,1511],{},"With margin around the feature, the frame hugs each coverage polygon plus a percentage, so every page fills its frame but the scale varies. With a predefined scale, the frame snaps to the nearest scale from a list, so scales are round numbers but framing varies. With a fixed scale, every page is at the same scale and small features sit in a lot of empty space.",[48,1513],{"x":50,"y":50,"width":1514,"height":1515,"fill":53},"760","244",[65,1517,1521],{"x":1518,"y":1519,"style":1520,"fill":69,"textAnchor":71},"380","26","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","Consistent framing or consistent scale — you cannot have both",[1523,1524,1525,1532,1538,1546,1551,1554,1557,1561],"g",{},[48,1526],{"x":1527,"y":1528,"width":1529,"height":1530,"rx":1531,"fill":61,"stroke":62,"style":63},"12","46","236","182","10",[65,1533,1537],{"x":1534,"y":1535,"style":1536,"fill":62,"textAnchor":71},"130","70","text-anchor:middle;font-size:11.5px;font-weight:bold;font-family:sans-serif","margin around feature",[48,1539],{"x":1540,"y":1541,"width":1542,"height":59,"fill":1543,"stroke":1544,"style":1545},"36","86","88","#e7e2d4","#59645f","stroke-width:1.3",[154,1547],{"d":1548,"fill":62,"fillOpacity":1549,"stroke":62,"style":1550},"M46 144 L48 96 L112 92 L116 146 Z",0.3,"stroke-width:1.6",[48,1552],{"x":1553,"y":1541,"width":1542,"height":59,"fill":1543,"stroke":1544,"style":1545},"136",[154,1555],{"d":1556,"fill":62,"fillOpacity":1549,"stroke":62,"style":1550},"M146 144 L148 96 L212 92 L216 146 Z",[65,1558,1560],{"x":1534,"y":1530,"style":1559,"fill":94,"textAnchor":71},"text-anchor:middle;font-size:10.5px;font-family:sans-serif","every page fills its frame",[65,1562,1564],{"x":1534,"y":1563,"style":1559,"fill":165,"textAnchor":71},"202","scale varies page to page",[1523,1566,1567,1570,1573,1576,1579,1582,1585,1588],{},[48,1568],{"x":1569,"y":1528,"width":1529,"height":1530,"rx":1531,"fill":61,"stroke":86,"style":63},"262",[65,1571,1572],{"x":1518,"y":1535,"style":1536,"fill":86,"textAnchor":71},"predefined scale",[48,1574],{"x":1575,"y":1541,"width":1542,"height":59,"fill":1543,"stroke":1544,"style":1545},"286",[154,1577],{"d":1578,"fill":86,"fillOpacity":1549,"stroke":86,"style":1550},"M298 142 L300 100 L356 96 L360 144 Z",[48,1580],{"x":1581,"y":1541,"width":1542,"height":59,"fill":1543,"stroke":1544,"style":1545},"386",[154,1583],{"d":1584,"fill":86,"fillOpacity":1549,"stroke":86,"style":1550},"M404 138 L406 108 L452 104 L456 140 Z",[65,1586,1587],{"x":1518,"y":1530,"style":1559,"fill":94,"textAnchor":71},"scales are round numbers",[65,1589,1590],{"x":1518,"y":1563,"style":1559,"fill":86,"textAnchor":71},"a readable compromise",[1523,1592,1593,1596,1600,1603,1606,1609,1612,1615],{},[48,1594],{"x":1595,"y":1528,"width":1529,"height":1530,"rx":1531,"fill":61,"stroke":165,"style":63},"512",[65,1597,1599],{"x":1598,"y":1535,"style":1536,"fill":165,"textAnchor":71},"630","fixed scale",[48,1601],{"x":1602,"y":1541,"width":1542,"height":59,"fill":1543,"stroke":1544,"style":1545},"536",[154,1604],{"d":1605,"fill":165,"fillOpacity":1549,"stroke":165,"style":1550},"M552 140 L554 104 L604 100 L608 142 Z",[48,1607],{"x":1608,"y":1541,"width":1542,"height":59,"fill":1543,"stroke":1544,"style":1545},"636",[154,1610],{"d":1611,"fill":165,"fillOpacity":1549,"stroke":165,"style":1550},"M666 126 L667 112 L690 110 L692 128 Z",[65,1613,1614],{"x":1598,"y":1530,"style":1559,"fill":94,"textAnchor":71},"pages are directly comparable",[65,1616,1617],{"x":1598,"y":1563,"style":1559,"fill":165,"textAnchor":71},"small features get lost",[193,1619,1621],{"id":1620},"filenames-and-page-order","Filenames and page order",[14,1623,1624,1625,1628,1629,1632],{},"An atlas that writes ",[212,1626,1627],{},"output_0.pdf"," through ",[212,1630,1631],{},"output_412.pdf"," is technically correct and useless. Both the order and the names come from expressions on the atlas.",[14,1634,1635],{},[32,1636,1639,1642,1645,1648,1651,1657,1661,1672,1675,1679,1687,1690,1694,1699,1703,1705,1708,1711],{"viewBox":1637,"role":35,"ariaLabel":1638,"xmlns":37},"0 0 760 232","Default numeric atlas filenames compared with expression-driven names built from a coverage attribute, and unsorted versus sorted page order",[40,1640,1641],{},"Naming and ordering the pages",[44,1643,1644],{},"Default output names are the layout name plus an index, which carry no information about what each page shows. An expression-driven filename built from a coverage attribute produces names a person can find. Similarly, an unsorted atlas follows feature order in the file, while a sort expression produces a predictable sequence.",[48,1646],{"x":50,"y":50,"width":1514,"height":1647,"fill":53},"232",[65,1649,1650],{"x":1518,"y":1519,"style":1520,"fill":69,"textAnchor":71},"Two expressions turn a pile of files into a deliverable",[48,1652],{"x":1653,"y":1528,"width":1654,"height":1655,"rx":1531,"fill":61,"stroke":1656,"style":63},"16","356","80","#b91c1c",[65,1658,1660],{"x":118,"y":1535,"style":1659,"fill":1656},"font-size:11.5px;font-weight:bold;font-family:sans-serif","default names",[1523,1662,1664,1668],{"style":1663},"font-size:11px;font-family:monospace",[65,1665,1667],{"x":118,"y":1666,"fill":94},"94","atlas_0.pdf  atlas_1.pdf",[65,1669,1671],{"x":118,"y":1670,"fill":94},"114","atlas_2.pdf  atlas_3.pdf",[48,1673],{"x":1674,"y":1528,"width":1654,"height":1655,"rx":1531,"fill":61,"stroke":120,"style":63},"388",[65,1676,1678],{"x":1677,"y":1535,"style":1659,"fill":120},"412","filenameExpression",[1523,1680,1681,1684],{"style":1663},[65,1682,1683],{"x":1677,"y":1666,"fill":94},"ashford.pdf  brant.pdf",[65,1685,1686],{"x":1677,"y":1670,"fill":94},"corran.pdf   dunmore.pdf",[48,1688],{"x":1653,"y":1689,"width":1654,"height":1655,"rx":1531,"fill":61,"stroke":165,"style":63},"138",[65,1691,1693],{"x":118,"y":1692,"style":1659,"fill":165},"162","unsorted",[65,1695,1698],{"x":118,"y":1696,"style":1697,"fill":94},"188","font-size:11px;font-family:sans-serif","follows the order features sit in the file",[65,1700,1702],{"x":118,"y":84,"style":1701,"fill":1544},"font-size:10.5px;font-family:sans-serif","which changes when the data is rewritten",[48,1704],{"x":1674,"y":1689,"width":1654,"height":1655,"rx":1531,"fill":61,"stroke":120,"style":63},[65,1706,1707],{"x":1677,"y":1692,"style":1659,"fill":120},"sortExpression",[65,1709,1710],{"x":1677,"y":1696,"style":1697,"fill":94},"the same order on every run",[65,1712,1713],{"x":1677,"y":84,"style":1701,"fill":1544},"essential for a bound document",[193,1715,1717],{"id":1716},"qgis-version-compatibility","QGIS Version Compatibility",[14,1719,1720,1721,1724],{},"Baseline: ",[204,1722,1723],{},"QGIS 3.34 LTR (Python 3.12)",". The atlas export API is stable across the current LTR and latest lines.",[1726,1727,1728,1741],"table",{},[1729,1730,1731],"thead",{},[1732,1733,1734,1738],"tr",{},[1735,1736,1737],"th",{},"QGIS \u002F Python",[1735,1739,1740],{},"Notes",[1742,1743,1744,1762,1772],"tbody",{},[1732,1745,1746,1750],{},[1747,1748,1749],"td",{},"3.28 LTR \u002F Py 3.9",[1747,1751,1752,1753,1755,1756,1758,1759,1761],{},"Atlas overloads of ",[212,1754,108],{}," available and return ",[212,1757,1095],{},". ",[212,1760,138],{}," present.",[1732,1763,1764,1769],{},[1747,1765,1766],{},[204,1767,1768],{},"3.34 LTR \u002F Py 3.12 (baseline)",[1747,1770,1771],{},"Recommended. Behaviour as documented here.",[1732,1773,1774,1777],{},[1747,1775,1776],{},"3.40 \u002F 3.44 \u002F Py 3.12",[1747,1778,1779,1780,1783],{},"Same API. Some ",[212,1781,1782],{},"PdfExportSettings"," fields added; existing fields unchanged.",[14,1785,1786,1787,482,1789,1792,1793,482,1796,1799,1800,1803],{},"The single biggest cross-version gotcha is the return signature: the ",[204,1788,1079],{},[212,1790,1791],{},"exportToPdf(atlas, ...)"," returns a tuple, while the ",[204,1794,1795],{},"single-layout",[212,1797,1798],{},"exportToPdf(path, ...)"," returns one status code. Unpack accordingly or you will hit a ",[212,1801,1802],{},"cannot unpack"," \u002F comparison error.",[193,1805,1807],{"id":1806},"troubleshooting","Troubleshooting",[14,1809,1810,1816,1817,1819,1820,1823,1824,1815],{},[204,1811,1812,1815],{},[212,1813,1814],{},"Atlas has 0 pages","."," The filter excluded everything, the coverage layer is empty, or you set the filter without calling ",[212,1818,794],{},". Verify the expression against the coverage layer's fields and confirm ",[212,1821,1822],{},"atlas.count()"," after ",[212,1825,794],{},[14,1827,1828,1834,1835,1837,1838,1840],{},[204,1829,1830,1833],{},[212,1831,1832],{},"cannot unpack non-iterable"," or a comparison failure on the export result."," You used the single-layout call signature. With an atlas, ",[212,1836,1791],{}," returns ",[212,1839,1095],{}," — unpack two values.",[14,1842,1843,1846],{},[204,1844,1845],{},"The PDF renders but the map frame does not move per page."," The map item is not atlas-controlled. In the layout, enable \"Controlled by atlas\" on the map item (or set it in code), otherwise every page shows the same extent.",[14,1848,1849,1852],{},[204,1850,1851],{},"Blank pages or missing layers."," A data source path in the project is broken, or layers are toggled off. Re-open the project in QGIS, fix broken sources, and confirm the relevant layers are visible before scripting the export.",[14,1854,1855,1858],{},[204,1856,1857],{},"Export fails with a file error on Windows."," The target PDF is open in a viewer, or antivirus\u002Fcloud-sync locks the folder. Export to a local temp directory, then move the files.",[193,1860,1862],{"id":1861},"conclusion","Conclusion",[14,1864,1865,1866,1869,1870,1872,1873,1876],{},"Generating an atlas PDF from PyQGIS is a tight three-step flow: load the project and layout, enable and configure the atlas (coverage, filter, page names, ",[212,1867,1868],{},"updateFeatures","), then export with the atlas-aware ",[212,1871,108],{}," overloads. Toggle one flag to switch between a combined book and per-feature sheets, and add a ",[212,1874,1875],{},"QgsApplication"," bootstrap to run the whole thing unattended. With the export driven by code, your map book regenerates automatically whenever the coverage data changes — no dialogs, no manual paging.",[193,1878,1880],{"id":1879},"frequently-asked-questions","Frequently Asked Questions",[14,1882,1883,1886,1887,1890,1891,1893,1894,1896],{},[204,1884,1885],{},"How do I produce one PDF per feature instead of a single document?","\nCall ",[212,1888,1889],{},"exporter.exportToPdfs(atlas, base_path, settings)"," instead of ",[212,1892,127],{},". Filenames come from the atlas ",[212,1895,1091],{},", so set that to something unique per feature.",[14,1898,1899,1905,1906,511,1908,1910],{},[204,1900,1901,1902,1904],{},"Do I need the QGIS GUI or ",[212,1903,1308],{}," to export an atlas?","\nNo. Atlas export depends only on the project and layout. Bootstrap with ",[212,1907,1392],{},[212,1909,1400],{},", and it runs fully headless.",[14,1912,1913,1916,1917,489,1919,489,1921,1924,1925,1927,1928,1815],{},[204,1914,1915],{},"Why is the export result not a simple success code?","\nThe atlas overloads of ",[212,1918,127],{},[212,1920,138],{},[212,1922,1923],{},"exportToImage"," return a ",[212,1926,1095],{}," tuple. Unpack both, then compare ",[212,1929,1930],{},"result == QgsLayoutExporter.Success",[14,1932,1933,1936,1937,511,1940,1943,1944,1947,1948,1815],{},[204,1934,1935],{},"Can I filter which features become pages?","\nYes. Call ",[212,1938,1939],{},"atlas.setFilterFeatures(True)",[212,1941,1942],{},"atlas.setFilterExpression(expr)"," with any valid QGIS expression over the coverage layer's fields, then ",[212,1945,1946],{},"atlas.updateFeatures()"," and check ",[212,1949,1822],{},[193,1951,1953],{"id":1952},"related","Related",[198,1955,1956,1960,1965],{},[201,1957,1958],{},[18,1959,21],{"href":20},[201,1961,1962],{},[18,1963,1964],{"href":28},"Exporting QGIS Layouts to PDF with PyQGIS",[201,1966,1967],{},[18,1968,1970],{"href":1969},"\u002Fpyqgis-cartography-visualization\u002Fgraduated-categorized-renderers\u002Fcreate-choropleth-map-pyqgis\u002F","Create a Choropleth Map in PyQGIS",[1972,1973,1974],"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 .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":250,"searchDepth":273,"depth":273,"links":1976},[1977,1978,1979,1980,1981,1982,1983,1984,1985,1986,1987,1988,1989,1990],{"id":195,"depth":273,"text":196},{"id":239,"depth":273,"text":240},{"id":518,"depth":273,"text":519},{"id":802,"depth":273,"text":803},{"id":1110,"depth":273,"text":1111},{"id":1301,"depth":273,"text":1302},{"id":1416,"depth":273,"text":1417},{"id":1495,"depth":273,"text":1496},{"id":1620,"depth":273,"text":1621},{"id":1716,"depth":273,"text":1717},{"id":1806,"depth":273,"text":1807},{"id":1861,"depth":273,"text":1862},{"id":1879,"depth":273,"text":1880},{"id":1952,"depth":273,"text":1953},"Generate an atlas PDF in PyQGIS by loading a project and layout, enabling the atlas, setting the coverage layer and filter, and exporting all pages.","md",{"slug":1994,"type":1995,"breadcrumb":1996,"datePublished":1997,"dateModified":1998},"generate-atlas-pdf-pyqgis","article","Generate an Atlas PDF","2025-03-18","2026-07-18","\u002Fspatial-data-processing-automation\u002Fautomating-atlas-map-series\u002Fgenerate-atlas-pdf-pyqgis",{"title":5,"description":1991},"spatial-data-processing-automation\u002Fautomating-atlas-map-series\u002Fgenerate-atlas-pdf-pyqgis\u002Findex","LDnBKz2j5kzwr96UYWoM6f0Yec_8n-8CgnaOOUXjp7g",1787823363447]