[{"data":1,"prerenderedAt":1603},["ShallowReactive",2],{"doc:\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Fpyqgis-script-to-calculate-polygon-areas":3},{"id":4,"title":5,"body":6,"description":1592,"extension":1593,"meta":1594,"navigation":171,"path":1599,"seo":1600,"stem":1601,"__hash__":1602},"docs\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Fpyqgis-script-to-calculate-polygon-areas\u002Findex.md","PyQGIS Script to Calculate Polygon Areas",{"type":7,"value":8,"toc":1577},"minimark",[9,13,35,44,49,90,110,114,125,423,451,456,468,562,586,778,782,790,934,956,960,963,1070,1094,1098,1107,1174,1180,1184,1187,1268,1271,1275,1282,1353,1378,1382,1448,1452,1464,1468,1482,1494,1513,1533,1546,1550,1573],[10,11,5],"h1",{"id":12},"pyqgis-script-to-calculate-polygon-areas",[14,15,16,17,21,22,26,27,30,31,34],"p",{},"To calculate polygon areas with PyQGIS, iterate the features of a polygon layer and read each geometry's area, then write the value into a numeric attribute field. The catch that trips up almost everyone is the coordinate reference system: ",[18,19,20],"code",{},"QgsGeometry.area()"," returns ",[23,24,25],"em",{},"planar"," area in the layer's own CRS units, so a projected CRS (UTM, a national grid) gives square meters while a geographic CRS such as WGS84\u002FEPSG:4326 gives meaningless square degrees. This page shows a correct, runnable script, the geodesic alternative that stays accurate without reprojecting, and the pitfalls that produce ",[18,28,29],{},"0.0"," or ",[18,32,33],{},"NaN"," values.",[14,36,37,38,43],{},"Adding a computed area column is one of the most common tasks in ",[39,40,42],"a",{"href":41},"\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002F","Vector Data Manipulation",": sizing parcels, reporting land-cover totals, or filtering polygons above a threshold. The same pattern — reference a layer, add a field, iterate, commit — underpins most attribute-writing scripts, so it is worth getting right once.",[45,46,48],"h2",{"id":47},"prerequisites","Prerequisites",[50,51,52,56,64,75],"ul",{},[53,54,55],"li",{},"QGIS 3.34 LTR (bundled Python 3.12), or any current 3.x release.",[53,57,58,59,63],{},"A ",[60,61,62],"strong",{},"polygon layer"," loaded in the project (Shapefile, GeoPackage, PostGIS, or FileGDB) with a writable data source.",[53,65,66,67,70,71,74],{},"Familiarity with the QGIS Python Console (",[18,68,69],{},"Plugins > Python Console",", or ",[18,72,73],{},"Ctrl+Alt+P",").",[53,76,77,78,81,82,85,86,89],{},"Ideally, a ",[60,79,80],{},"projected CRS"," on the layer so planar area comes out in linear units. Check with ",[18,83,84],{},"layer.crs().isGeographic()"," — if it returns ",[18,87,88],{},"True",", use the geodesic recipe further down instead of reprojecting.",[14,91,92,93,96,97,100,101,104,105,109],{},"In the Python Console ",[18,94,95],{},"iface"," and ",[18,98,99],{},"processing"," are already imported. In a standalone script you must first initialize ",[18,102,103],{},"QgsApplication"," and Processing — see ",[39,106,108],{"href":107},"\u002Fspatial-data-processing-automation\u002Fbatch-processing-with-pyqgis\u002Frun-processing-algorithm-from-script\u002F","Run a Processing Algorithm from a Script in PyQGIS",".",[45,111,113],{"id":112},"add-an-area-field-and-populate-it","Add an Area Field and Populate It",[14,115,116,117,120,121,124],{},"Run this in the Python Console, or save it as a ",[18,118,119],{},".py"," file and execute it as a script. It references the target layer by name, adds a ",[18,122,123],{},"Double"," field if it is missing, then writes the planar area of every valid feature inside a single edit transaction.",[126,127,132],"pre",{"className":128,"code":129,"language":130,"meta":131,"style":131},"language-python shiki shiki-themes github-dark","from qgis.core import QgsProject, QgsField, edit\nfrom qgis.PyQt.QtCore import QVariant\n\n# 1. Reference the target polygon layer\nlayer_name = \"your_polygon_layer\"\nlayers = QgsProject.instance().mapLayersByName(layer_name)\nif not layers:\n    raise RuntimeError(f\"Layer '{layer_name}' not found in project.\")\nlayer = layers[0]\n\n# 2. Add the output field if it does not already exist\nfield_name = \"area_sqm\"\nif field_name not in layer.fields().names():\n    layer.dataProvider().addAttributes([QgsField(field_name, QVariant.Double)])\n    layer.updateFields()\n\n# 3. Calculate and write areas inside one transaction\nwith edit(layer):\n    for feat in layer.getFeatures():\n        geom = feat.geometry()\n        if geom.isGeosValid() and not geom.isEmpty():\n            feat.setAttribute(field_name, geom.area())\n            layer.updateFeature(feat)\n\nprint(f\"Area calculation complete for {layer.featureCount()} features.\")\n","python","",[18,133,134,153,166,173,180,193,204,216,250,267,272,278,289,306,312,318,323,329,338,353,364,381,387,393,398],{"__ignoreMap":131},[135,136,139,143,147,150],"span",{"class":137,"line":138},"line",1,[135,140,142],{"class":141},"snl16","from",[135,144,146],{"class":145},"s95oV"," qgis.core ",[135,148,149],{"class":141},"import",[135,151,152],{"class":145}," QgsProject, QgsField, edit\n",[135,154,156,158,161,163],{"class":137,"line":155},2,[135,157,142],{"class":141},[135,159,160],{"class":145}," qgis.PyQt.QtCore ",[135,162,149],{"class":141},[135,164,165],{"class":145}," QVariant\n",[135,167,169],{"class":137,"line":168},3,[135,170,172],{"emptyLinePlaceholder":171},true,"\n",[135,174,176],{"class":137,"line":175},4,[135,177,179],{"class":178},"sjoCn","# 1. Reference the target polygon layer\n",[135,181,183,186,189],{"class":137,"line":182},5,[135,184,185],{"class":145},"layer_name ",[135,187,188],{"class":141},"=",[135,190,192],{"class":191},"sU2Wk"," \"your_polygon_layer\"\n",[135,194,196,199,201],{"class":137,"line":195},6,[135,197,198],{"class":145},"layers ",[135,200,188],{"class":141},[135,202,203],{"class":145}," QgsProject.instance().mapLayersByName(layer_name)\n",[135,205,207,210,213],{"class":137,"line":206},7,[135,208,209],{"class":141},"if",[135,211,212],{"class":141}," not",[135,214,215],{"class":145}," layers:\n",[135,217,219,222,226,229,232,235,238,241,244,247],{"class":137,"line":218},8,[135,220,221],{"class":141},"    raise",[135,223,225],{"class":224},"sDLfK"," RuntimeError",[135,227,228],{"class":145},"(",[135,230,231],{"class":141},"f",[135,233,234],{"class":191},"\"Layer '",[135,236,237],{"class":224},"{",[135,239,240],{"class":145},"layer_name",[135,242,243],{"class":224},"}",[135,245,246],{"class":191},"' not found in project.\"",[135,248,249],{"class":145},")\n",[135,251,253,256,258,261,264],{"class":137,"line":252},9,[135,254,255],{"class":145},"layer ",[135,257,188],{"class":141},[135,259,260],{"class":145}," layers[",[135,262,263],{"class":224},"0",[135,265,266],{"class":145},"]\n",[135,268,270],{"class":137,"line":269},10,[135,271,172],{"emptyLinePlaceholder":171},[135,273,275],{"class":137,"line":274},11,[135,276,277],{"class":178},"# 2. Add the output field if it does not already exist\n",[135,279,281,284,286],{"class":137,"line":280},12,[135,282,283],{"class":145},"field_name ",[135,285,188],{"class":141},[135,287,288],{"class":191}," \"area_sqm\"\n",[135,290,292,294,297,300,303],{"class":137,"line":291},13,[135,293,209],{"class":141},[135,295,296],{"class":145}," field_name ",[135,298,299],{"class":141},"not",[135,301,302],{"class":141}," in",[135,304,305],{"class":145}," layer.fields().names():\n",[135,307,309],{"class":137,"line":308},14,[135,310,311],{"class":145},"    layer.dataProvider().addAttributes([QgsField(field_name, QVariant.Double)])\n",[135,313,315],{"class":137,"line":314},15,[135,316,317],{"class":145},"    layer.updateFields()\n",[135,319,321],{"class":137,"line":320},16,[135,322,172],{"emptyLinePlaceholder":171},[135,324,326],{"class":137,"line":325},17,[135,327,328],{"class":178},"# 3. Calculate and write areas inside one transaction\n",[135,330,332,335],{"class":137,"line":331},18,[135,333,334],{"class":141},"with",[135,336,337],{"class":145}," edit(layer):\n",[135,339,341,344,347,350],{"class":137,"line":340},19,[135,342,343],{"class":141},"    for",[135,345,346],{"class":145}," feat ",[135,348,349],{"class":141},"in",[135,351,352],{"class":145}," layer.getFeatures():\n",[135,354,356,359,361],{"class":137,"line":355},20,[135,357,358],{"class":145},"        geom ",[135,360,188],{"class":141},[135,362,363],{"class":145}," feat.geometry()\n",[135,365,367,370,373,376,378],{"class":137,"line":366},21,[135,368,369],{"class":141},"        if",[135,371,372],{"class":145}," geom.isGeosValid() ",[135,374,375],{"class":141},"and",[135,377,212],{"class":141},[135,379,380],{"class":145}," geom.isEmpty():\n",[135,382,384],{"class":137,"line":383},22,[135,385,386],{"class":145},"            feat.setAttribute(field_name, geom.area())\n",[135,388,390],{"class":137,"line":389},23,[135,391,392],{"class":145},"            layer.updateFeature(feat)\n",[135,394,396],{"class":137,"line":395},24,[135,397,172],{"emptyLinePlaceholder":171},[135,399,401,404,406,408,411,413,416,418,421],{"class":137,"line":400},25,[135,402,403],{"class":224},"print",[135,405,228],{"class":145},[135,407,231],{"class":141},[135,409,410],{"class":191},"\"Area calculation complete for ",[135,412,237],{"class":224},[135,414,415],{"class":145},"layer.featureCount()",[135,417,243],{"class":224},[135,419,420],{"class":191}," features.\"",[135,422,249],{"class":145},[14,424,425,428,429,432,433,436,437,439,440,443,444,96,447,450],{},[60,426,427],{},"Breakdown:"," ",[18,430,431],{},"mapLayersByName"," resolves the layer by its tree name; guarding against an empty list gives a clear error instead of an ",[18,434,435],{},"IndexError",". ",[18,438,20],{}," returns planar area in the CRS's units — square meters for a metric projected CRS. The ",[18,441,442],{},"with edit(layer):"," context manager batches every change into one commit, which is dramatically faster than per-feature disk writes and rolls back cleanly if an exception is raised. The ",[18,445,446],{},"isGeosValid()",[18,448,449],{},"isEmpty()"," guards skip self-intersecting rings, collapsed geometries, and empty features that would otherwise poison the result. Multipolygons are aggregated by GEOS automatically, so no manual flattening is needed.",[452,453,455],"h3",{"id":454},"geodesic-area-without-reprojecting","Geodesic Area Without Reprojecting",[14,457,458,459,462,463,467],{},"When the layer is in a geographic CRS and you cannot (or do not want to) reproject it, switch from planar to ellipsoidal measurement with ",[18,460,461],{},"QgsDistanceArea",". This is the accurate way to measure large polygons that span several UTM zones, and it is closely tied to correct ",[39,464,466],{"href":465},"\u002Fspatial-data-processing-automation\u002Fcoordinate-reference-systems\u002F","coordinate reference system"," handling.",[126,469,471],{"className":128,"code":470,"language":130,"meta":131,"style":131},"from qgis.core import QgsDistanceArea, QgsProject, edit\n\nda = QgsDistanceArea()\nda.setSourceCrs(layer.crs(), QgsProject.instance().transformContext())\nda.setEllipsoid(\"WGS84\")\n\nwith edit(layer):\n    for feat in layer.getFeatures():\n        geom = feat.geometry()\n        if geom.isGeosValid() and not geom.isEmpty():\n            feat.setAttribute(field_name, da.measureArea(geom))\n            layer.updateFeature(feat)\n",[18,472,473,484,488,498,503,513,517,523,533,541,553,558],{"__ignoreMap":131},[135,474,475,477,479,481],{"class":137,"line":138},[135,476,142],{"class":141},[135,478,146],{"class":145},[135,480,149],{"class":141},[135,482,483],{"class":145}," QgsDistanceArea, QgsProject, edit\n",[135,485,486],{"class":137,"line":155},[135,487,172],{"emptyLinePlaceholder":171},[135,489,490,493,495],{"class":137,"line":168},[135,491,492],{"class":145},"da ",[135,494,188],{"class":141},[135,496,497],{"class":145}," QgsDistanceArea()\n",[135,499,500],{"class":137,"line":175},[135,501,502],{"class":145},"da.setSourceCrs(layer.crs(), QgsProject.instance().transformContext())\n",[135,504,505,508,511],{"class":137,"line":182},[135,506,507],{"class":145},"da.setEllipsoid(",[135,509,510],{"class":191},"\"WGS84\"",[135,512,249],{"class":145},[135,514,515],{"class":137,"line":195},[135,516,172],{"emptyLinePlaceholder":171},[135,518,519,521],{"class":137,"line":206},[135,520,334],{"class":141},[135,522,337],{"class":145},[135,524,525,527,529,531],{"class":137,"line":218},[135,526,343],{"class":141},[135,528,346],{"class":145},[135,530,349],{"class":141},[135,532,352],{"class":145},[135,534,535,537,539],{"class":137,"line":252},[135,536,358],{"class":145},[135,538,188],{"class":141},[135,540,363],{"class":145},[135,542,543,545,547,549,551],{"class":137,"line":269},[135,544,369],{"class":141},[135,546,372],{"class":145},[135,548,375],{"class":141},[135,550,212],{"class":141},[135,552,380],{"class":145},[135,554,555],{"class":137,"line":274},[135,556,557],{"class":145},"            feat.setAttribute(field_name, da.measureArea(geom))\n",[135,559,560],{"class":137,"line":280},[135,561,392],{"class":145},[14,563,564,428,566,569,570,573,574,577,578,581,582,585],{},[60,565,427],{},[18,567,568],{},"setSourceCrs"," tells the calculator what projection the incoming geometries use, and ",[18,571,572],{},"setEllipsoid(\"WGS84\")"," selects the reference ellipsoid for the on-the-ellipsoid math. ",[18,575,576],{},"da.measureArea()"," then returns square meters regardless of the layer's stored CRS, so you never have to physically reproject the data. Newer QGIS builds may emit a deprecation note for ",[18,579,580],{},"measureArea","; if so, ",[18,583,584],{},"da.measureAreaGeometry(geom)"," is the drop-in successor.",[14,587,588],{},[589,590,595,599,603,620,626,635,644,649,654,659,664,668,675,678,683,688,693,698,702,708,712,716,721,725,729,732,735,740,743,748,752,756,760,764,767,771,775],"svg",{"viewBox":591,"role":592,"ariaLabel":593,"xmlns":594},"0 0 760 440","img","Decision flowchart for calculating polygon area in PyQGIS based on whether the layer CRS is geographic or projected","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[596,597,598],"title",{},"Planar versus geodesic area decision in PyQGIS",[600,601,602],"desc",{},"Starting from a polygon layer, test layer.crs().isGeographic(). If false the CRS is projected, so QgsGeometry.area() returns valid square metres. If true the CRS is geographic such as EPSG:4326, where planar area() returns meaningless square degrees; instead either reproject to UTM and call area(), or call QgsDistanceArea.measureArea() with the WGS84 ellipsoid, both of which return valid square metres.",[604,605,606],"defs",{},[607,608,615],"marker",{"id":609,"viewBox":610,"refX":611,"refY":612,"markerWidth":613,"markerHeight":613,"orient":614},"areaarrow","0 0 10 10","8","5","7","auto-start-reverse",[616,617],"path",{"d":618,"fill":619},"M0 0 L10 5 L0 10 z","#2f3b35",[621,622],"rect",{"x":263,"y":263,"width":623,"height":624,"fill":625},"760","440","#f6f3ea",[621,627],{"x":628,"y":629,"width":630,"height":631,"rx":632,"fill":633,"stroke":619,"style":634},"300","24","160","46","10","#fffdf7","stroke-width:2.5",[636,637,643],"text",{"x":638,"y":639,"style":640,"fill":641,"textAnchor":642},"380","53","text-anchor:middle;font-size:15px;font-weight:bold;font-family:sans-serif","#17211d","middle","Polygon layer",[137,645],{"x1":638,"y1":646,"x2":638,"y2":647,"stroke":619,"style":648},"70","90","stroke-width:2.5;marker-end:url(#areaarrow)",[650,651],"polygon",{"points":652,"fill":633,"stroke":653,"style":634},"380,92 474,140 380,188 286,140","#0f766e",[636,655,658],{"x":638,"y":656,"style":657,"fill":641,"textAnchor":642},"136","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","layer.crs()",[636,660,663],{"x":638,"y":661,"style":662,"fill":619,"textAnchor":642},"154","text-anchor:middle;font-size:12px;font-family:sans-serif",".isGeographic()?",[616,665],{"d":666,"fill":667,"stroke":619,"style":648},"M286 140 H175 V230","none",[636,669,674],{"x":670,"y":671,"style":672,"fill":673,"textAnchor":642},"212","132","text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif","#15803d","False · projected",[616,676],{"d":677,"fill":667,"stroke":619,"style":648},"M474 140 H560 V206",[636,679,682],{"x":680,"y":671,"style":672,"fill":681,"textAnchor":642},"548","#2563eb","True · geographic",[621,684],{"x":646,"y":685,"width":686,"height":687,"rx":632,"fill":633,"stroke":673,"style":634},"230","210","56",[636,689,20],{"x":690,"y":691,"style":692,"fill":641,"textAnchor":642},"175","254","text-anchor:middle;font-size:13px;font-weight:bold;font-family:sans-serif",[636,694,697],{"x":690,"y":695,"style":696,"fill":619,"textAnchor":642},"274","text-anchor:middle;font-size:11px;font-family:sans-serif","planar, in CRS linear units",[137,699],{"x1":690,"y1":700,"x2":690,"y2":701,"stroke":619,"style":648},"286","320",[621,703],{"x":704,"y":701,"width":705,"height":706,"rx":632,"fill":633,"stroke":673,"style":707},"80","190","58","stroke-width:2",[636,709,711],{"x":690,"y":710,"style":692,"fill":673,"textAnchor":642},"346","✓ Square metres",[636,713,715],{"x":690,"y":714,"style":696,"fill":619,"textAnchor":642},"366","fast & exact on a metric CRS",[621,717],{"x":718,"y":719,"width":701,"height":706,"rx":632,"fill":633,"stroke":720,"style":634},"400","206","#b45309",[636,722,724],{"x":723,"y":685,"style":657,"fill":720,"textAnchor":642},"560","Planar area() here → square degrees",[636,726,728],{"x":723,"y":727,"style":696,"fill":619,"textAnchor":642},"250","✗ meaningless — degrees are not a length",[616,730],{"d":731,"fill":667,"stroke":619,"style":634},"M560 264 V294",[616,733],{"d":734,"fill":667,"stroke":619,"style":634},"M430 294 H640",[137,736],{"x1":737,"y1":738,"x2":737,"y2":739,"stroke":619,"style":648},"430","294","318",[137,741],{"x1":742,"y1":738,"x2":742,"y2":739,"stroke":619,"style":648},"640",[621,744],{"x":745,"y":739,"width":746,"height":747,"rx":632,"fill":633,"stroke":673,"style":634},"336","188","96",[636,749,751],{"x":737,"y":750,"style":657,"fill":641,"textAnchor":642},"344","Reproject to UTM",[636,753,755],{"x":737,"y":754,"style":696,"fill":619,"textAnchor":642},"364","then call .area()",[636,757,759],{"x":737,"y":758,"style":657,"fill":673,"textAnchor":642},"398","✓ square metres",[621,761],{"x":762,"y":739,"width":763,"height":747,"rx":632,"fill":633,"stroke":673,"style":634},"542","196",[636,765,461],{"x":742,"y":766,"style":657,"fill":641,"textAnchor":642},"342",[636,768,770],{"x":742,"y":769,"style":696,"fill":619,"textAnchor":642},"361",".measureArea() ·",[636,772,774],{"x":742,"y":773,"style":696,"fill":619,"textAnchor":642},"378","WGS84 ellipsoid",[636,776,759],{"x":742,"y":777,"style":657,"fill":673,"textAnchor":642},"402",[452,779,781],{"id":780},"no-script-alternative-the-field-calculator-algorithm","No-Script Alternative: the Field Calculator Algorithm",[14,783,784,785,789],{},"If direct editing fails because of a provider lock or a read-only source, the native field-calculator algorithm writes the area to a fresh output layer instead of mutating the original. This is often the safest option inside larger ",[39,786,788],{"href":787},"\u002Fspatial-data-processing-automation\u002F","Spatial Data Processing & Automation"," pipelines:",[126,791,793],{"className":128,"code":792,"language":130,"meta":131,"style":131},"import processing\nfrom qgis.core import QgsProject\n\nresult = processing.run(\"native:fieldcalculator\", {\n    \"INPUT\": layer,\n    \"FIELD_NAME\": \"area_sqm\",\n    \"FIELD_TYPE\": 0,        # 0 = Float\n    \"FIELD_LENGTH\": 20,\n    \"FIELD_PRECISION\": 6,\n    \"FORMULA\": \"$area\",\n    \"OUTPUT\": \"TEMPORARY_OUTPUT\",\n})\nQgsProject.instance().addMapLayer(result[\"OUTPUT\"])\n",[18,794,795,802,813,817,833,841,855,870,882,894,906,918,923],{"__ignoreMap":131},[135,796,797,799],{"class":137,"line":138},[135,798,149],{"class":141},[135,800,801],{"class":145}," processing\n",[135,803,804,806,808,810],{"class":137,"line":155},[135,805,142],{"class":141},[135,807,146],{"class":145},[135,809,149],{"class":141},[135,811,812],{"class":145}," QgsProject\n",[135,814,815],{"class":137,"line":168},[135,816,172],{"emptyLinePlaceholder":171},[135,818,819,822,824,827,830],{"class":137,"line":175},[135,820,821],{"class":145},"result ",[135,823,188],{"class":141},[135,825,826],{"class":145}," processing.run(",[135,828,829],{"class":191},"\"native:fieldcalculator\"",[135,831,832],{"class":145},", {\n",[135,834,835,838],{"class":137,"line":182},[135,836,837],{"class":191},"    \"INPUT\"",[135,839,840],{"class":145},": layer,\n",[135,842,843,846,849,852],{"class":137,"line":195},[135,844,845],{"class":191},"    \"FIELD_NAME\"",[135,847,848],{"class":145},": ",[135,850,851],{"class":191},"\"area_sqm\"",[135,853,854],{"class":145},",\n",[135,856,857,860,862,864,867],{"class":137,"line":206},[135,858,859],{"class":191},"    \"FIELD_TYPE\"",[135,861,848],{"class":145},[135,863,263],{"class":224},[135,865,866],{"class":145},",        ",[135,868,869],{"class":178},"# 0 = Float\n",[135,871,872,875,877,880],{"class":137,"line":218},[135,873,874],{"class":191},"    \"FIELD_LENGTH\"",[135,876,848],{"class":145},[135,878,879],{"class":224},"20",[135,881,854],{"class":145},[135,883,884,887,889,892],{"class":137,"line":252},[135,885,886],{"class":191},"    \"FIELD_PRECISION\"",[135,888,848],{"class":145},[135,890,891],{"class":224},"6",[135,893,854],{"class":145},[135,895,896,899,901,904],{"class":137,"line":269},[135,897,898],{"class":191},"    \"FORMULA\"",[135,900,848],{"class":145},[135,902,903],{"class":191},"\"$area\"",[135,905,854],{"class":145},[135,907,908,911,913,916],{"class":137,"line":274},[135,909,910],{"class":191},"    \"OUTPUT\"",[135,912,848],{"class":145},[135,914,915],{"class":191},"\"TEMPORARY_OUTPUT\"",[135,917,854],{"class":145},[135,919,920],{"class":137,"line":280},[135,921,922],{"class":145},"})\n",[135,924,925,928,931],{"class":137,"line":291},[135,926,927],{"class":145},"QgsProject.instance().addMapLayer(result[",[135,929,930],{"class":191},"\"OUTPUT\"",[135,932,933],{"class":145},"])\n",[14,935,936,938,939,942,943,946,947,950,951,955],{},[60,937,427],{}," The ",[18,940,941],{},"$area"," expression uses the project's ellipsoid setting when one is configured, so it can already return ellipsoidal area without extra code. ",[18,944,945],{},"TEMPORARY_OUTPUT"," produces a scratch layer that leaves your source untouched — swap it for a ",[18,948,949],{},".gpkg"," path to persist the result. This is the same approach used for other attribute jobs across the vector toolset, such as ",[39,952,954],{"href":953},"\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Fmerge-multiple-shapefiles-pyqgis\u002F","merging multiple shapefiles"," before summarizing them.",[452,957,959],{"id":958},"convert-to-hectares-or-square-kilometers","Convert to Hectares or Square Kilometers",[14,961,962],{},"Area rarely stays in square meters for reporting. Derive additional columns in the same loop rather than post-processing:",[126,964,966],{"className":128,"code":965,"language":130,"meta":131,"style":131},"with edit(layer):\n    for feat in layer.getFeatures():\n        geom = feat.geometry()\n        if geom.isGeosValid() and not geom.isEmpty():\n            sqm = geom.area()\n            feat.setAttribute(\"area_sqm\", sqm)\n            feat.setAttribute(\"area_ha\", sqm \u002F 10_000)      # hectares\n            feat.setAttribute(\"area_sqkm\", sqm \u002F 1_000_000)  # square km\n            layer.updateFeature(feat)\n",[18,967,968,974,984,992,1004,1014,1024,1046,1066],{"__ignoreMap":131},[135,969,970,972],{"class":137,"line":138},[135,971,334],{"class":141},[135,973,337],{"class":145},[135,975,976,978,980,982],{"class":137,"line":155},[135,977,343],{"class":141},[135,979,346],{"class":145},[135,981,349],{"class":141},[135,983,352],{"class":145},[135,985,986,988,990],{"class":137,"line":168},[135,987,358],{"class":145},[135,989,188],{"class":141},[135,991,363],{"class":145},[135,993,994,996,998,1000,1002],{"class":137,"line":175},[135,995,369],{"class":141},[135,997,372],{"class":145},[135,999,375],{"class":141},[135,1001,212],{"class":141},[135,1003,380],{"class":145},[135,1005,1006,1009,1011],{"class":137,"line":182},[135,1007,1008],{"class":145},"            sqm ",[135,1010,188],{"class":141},[135,1012,1013],{"class":145}," geom.area()\n",[135,1015,1016,1019,1021],{"class":137,"line":195},[135,1017,1018],{"class":145},"            feat.setAttribute(",[135,1020,851],{"class":191},[135,1022,1023],{"class":145},", sqm)\n",[135,1025,1026,1028,1031,1034,1037,1040,1043],{"class":137,"line":206},[135,1027,1018],{"class":145},[135,1029,1030],{"class":191},"\"area_ha\"",[135,1032,1033],{"class":145},", sqm ",[135,1035,1036],{"class":141},"\u002F",[135,1038,1039],{"class":224}," 10_000",[135,1041,1042],{"class":145},")      ",[135,1044,1045],{"class":178},"# hectares\n",[135,1047,1048,1050,1053,1055,1057,1060,1063],{"class":137,"line":218},[135,1049,1018],{"class":145},[135,1051,1052],{"class":191},"\"area_sqkm\"",[135,1054,1033],{"class":145},[135,1056,1036],{"class":141},[135,1058,1059],{"class":224}," 1_000_000",[135,1061,1062],{"class":145},")  ",[135,1064,1065],{"class":178},"# square km\n",[135,1067,1068],{"class":137,"line":252},[135,1069,392],{"class":145},[14,1071,1072,1074,1075,1078,1079,1082,1083,96,1086,1089,1090,1093],{},[60,1073,427],{}," One square meter is ",[18,1076,1077],{},"1e-4"," hectares and ",[18,1080,1081],{},"1e-6"," square kilometers, so the conversions are plain division. Add the ",[18,1084,1085],{},"area_ha",[18,1087,1088],{},"area_sqkm"," fields the same way as ",[18,1091,1092],{},"area_sqm"," before the loop runs.",[45,1095,1097],{"id":1096},"planar-area-versus-ellipsoidal-area","Planar area versus ellipsoidal area",[14,1099,1100,1103,1104,1106],{},[18,1101,1102],{},"geometry.area()"," measures on the flat plane of the layer's CRS. ",[18,1105,461],{}," with an ellipsoid set measures on the curved Earth. For a single parcel the two agree closely; for a region they do not, and for a layer in EPSG:4326 the planar figure is not an area at all.",[14,1108,1109],{},[589,1110,1113,1116,1119,1122,1127,1133,1138,1143,1147,1150,1154,1158,1161,1164,1167,1171],{"viewBox":1111,"role":592,"ariaLabel":1112,"xmlns":594},"0 0 760 252","The same polygon measured three ways: square degrees in a geographic CRS, planar square metres in a projected CRS, and ellipsoidal square metres, with only the last two being meaningful",[596,1114,1115],{},"Three area figures for one polygon",[600,1117,1118],{},"One parcel is measured three ways. In EPSG:4326 the area comes out as a tiny number in square degrees, which is not a unit of area. In a UTM zone the planar area is in square metres and close to correct. The ellipsoidal measurement is in square metres and accounts for the curvature of the Earth, differing from the planar figure by a fraction of a percent at parcel scale and much more at regional scale.",[621,1120],{"x":263,"y":263,"width":623,"height":1121,"fill":625},"252",[636,1123,1126],{"x":638,"y":1124,"style":1125,"fill":641,"textAnchor":642},"26","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","Only two of these three numbers are areas",[621,1128],{"x":1129,"y":631,"width":1130,"height":1131,"rx":632,"fill":633,"stroke":1132,"style":634},"16","728","60","#b91c1c",[636,1134,1137],{"x":1135,"y":646,"style":1136,"fill":1132},"40","font-size:11.5px;font-weight:bold;font-family:sans-serif","EPSG:4326 · geometry.area()",[636,1139,1142],{"x":638,"y":1140,"style":1141,"fill":619,"textAnchor":642},"84","text-anchor:middle;font-size:14px;font-family:monospace","0.00000412",[636,1144,1146],{"x":723,"y":1140,"style":1145,"fill":1132},"font-size:11px;font-family:sans-serif","square degrees — meaningless",[621,1148],{"x":1129,"y":1149,"width":1130,"height":1131,"rx":632,"fill":633,"stroke":720,"style":634},"118",[636,1151,1153],{"x":1135,"y":1152,"style":1136,"fill":720},"142","EPSG:32633 · geometry.area()",[636,1155,1157],{"x":638,"y":1156,"style":1141,"fill":619,"textAnchor":642},"156","4 218.62 m²",[636,1159,1160],{"x":723,"y":1156,"style":1145,"fill":619},"planar — good at parcel scale",[621,1162],{"x":1129,"y":705,"width":1130,"height":1163,"rx":632,"fill":633,"stroke":673,"style":634},"52",[636,1165,1166],{"x":1135,"y":670,"style":1136,"fill":673},"QgsDistanceArea · measureArea()",[636,1168,1170],{"x":638,"y":1169,"style":1141,"fill":619,"textAnchor":642},"226","4 219.05 m²",[636,1172,1173],{"x":723,"y":1169,"style":1145,"fill":673},"ellipsoidal — correct anywhere",[14,1175,1176,1177,1179],{},"The rule of thumb is simple: if the layer might ever be in a geographic CRS, or the polygons might ever be larger than a few square kilometres, use ",[18,1178,461],{},". It works correctly on lat\u002Flon data without reprojection, which alone usually justifies the three extra lines of setup.",[45,1181,1183],{"id":1182},"rounding-units-and-what-to-store","Rounding, units and what to store",[14,1185,1186],{},"An area field is a derived value, and how it is stored decides whether it can be trusted a year later.",[14,1188,1189],{},[589,1190,1193,1196,1199,1202,1205,1209,1214,1219,1223,1226,1229,1232,1235,1238,1241,1244,1248,1264],{"viewBox":1191,"role":592,"ariaLabel":1192,"xmlns":594},"0 0 760 234","Three storage choices for a computed area: raw square metres at full precision, rounded hectares, and both a value column and a units column",[596,1194,1195],{},"What to store alongside a computed area",[600,1197,1198],{},"Storing raw square metres at full precision is precise but implies accuracy the source geometry does not have. Storing rounded hectares alone loses the unit, so a later reader cannot tell what the number means. Storing a rounded value alongside an explicit units column and the calculation date is unambiguous and auditable.",[621,1200],{"x":263,"y":263,"width":623,"height":1201,"fill":625},"234",[636,1203,1204],{"x":638,"y":1124,"style":1125,"fill":641,"textAnchor":642},"A number without its unit is a future support ticket",[621,1206],{"x":1129,"y":631,"width":1207,"height":1208,"rx":632,"fill":633,"stroke":720,"style":707},"236","172",[636,1210,1213],{"x":1211,"y":646,"style":1212,"fill":720,"textAnchor":642},"134","text-anchor:middle;font-size:11.5px;font-weight:bold;font-family:sans-serif","raw m²",[636,1215,1218],{"x":1211,"y":1216,"style":1217,"fill":619,"textAnchor":642},"112","text-anchor:middle;font-size:12px;font-family:monospace","4218.6249183",[636,1220,1222],{"x":1211,"y":1221,"style":696,"fill":619,"textAnchor":642},"152","precise to a micron",[636,1224,1225],{"x":1211,"y":1208,"style":696,"fill":720,"textAnchor":642},"the survey was not",[621,1227],{"x":1228,"y":631,"width":1207,"height":1208,"rx":632,"fill":633,"stroke":1132,"style":707},"262",[636,1230,1231],{"x":638,"y":646,"style":1212,"fill":1132,"textAnchor":642},"bare number",[636,1233,1234],{"x":638,"y":1216,"style":1217,"fill":619,"textAnchor":642},"0.42",[636,1236,1237],{"x":638,"y":1221,"style":696,"fill":619,"textAnchor":642},"hectares? acres?",[636,1239,1240],{"x":638,"y":1208,"style":696,"fill":1132,"textAnchor":642},"nobody can tell later",[621,1242],{"x":1243,"y":631,"width":1207,"height":1208,"rx":632,"fill":633,"stroke":673,"style":634},"508",[636,1245,1247],{"x":1246,"y":646,"style":1212,"fill":673,"textAnchor":642},"626","value + unit + date",[1249,1250,1252,1257,1261],"g",{"style":1251},"font-size:11px;font-family:monospace",[636,1253,1256],{"x":1254,"y":1255,"fill":619},"530","104","area_value  0.4219",[636,1258,1260],{"x":1254,"y":1259,"fill":619},"128","area_unit   ha",[636,1262,1263],{"x":1254,"y":1221,"fill":619},"area_calc   2026-08-01",[636,1265,1267],{"x":1246,"y":1266,"style":696,"fill":673,"textAnchor":642},"186","unambiguous and auditable",[14,1269,1270],{},"Recording the calculation date matters more than it sounds. Areas go stale the moment geometry is edited, and a date column is the only cheap way to spot a value computed before the last round of boundary corrections.",[45,1272,1274],{"id":1273},"qgis-version-compatibility","QGIS Version Compatibility",[14,1276,1277,1278,1281],{},"The examples target ",[60,1279,1280],{},"QGIS 3.34 LTR"," (Python 3.12). The area APIs are stable across all current 3.x releases.",[1283,1284,1285,1301],"table",{},[1286,1287,1288],"thead",{},[1289,1290,1291,1295,1298],"tr",{},[1292,1293,1294],"th",{},"QGIS version",[1292,1296,1297],{},"Python",[1292,1299,1300],{},"Notes",[1302,1303,1304,1325,1336],"tbody",{},[1289,1305,1306,1310,1313],{},[1307,1308,1309],"td",{},"3.28 LTR",[1307,1311,1312],{},"3.9",[1307,1314,1315,1318,1319,1321,1322,1324],{},[18,1316,1317],{},"edit()",", ",[18,1320,20],{},", and ",[18,1323,461],{}," all identical.",[1289,1326,1327,1330,1333],{},[1307,1328,1329],{},"3.34 LTR",[1307,1331,1332],{},"3.12",[1307,1334,1335],{},"Baseline for this page.",[1289,1337,1338,1341,1343],{},[1307,1339,1340],{},"3.40 \u002F 3.44",[1307,1342,1332],{},[1307,1344,1345,1346,1349,1350,109],{},"Same API; ",[18,1347,1348],{},"QgsDistanceArea.measureArea()"," may warn — prefer ",[18,1351,1352],{},"measureAreaGeometry()",[14,1354,1355,1356,1359,1360,1363,1364,1366,1367,1370,1371,1374,1375,1377],{},"Two version notes matter in practice. First, the legacy QGIS 2.x ",[18,1357,1358],{},"startEditing()"," \u002F ",[18,1361,1362],{},"commitChanges()"," pattern still works but is superseded by the ",[18,1365,1317],{}," context manager shown here. Second, in QGIS 3.36+ ",[18,1368,1369],{},"QVariant.Double"," can be replaced by the newer ",[18,1372,1373],{},"QMetaType"," field constructor, but ",[18,1376,1369],{}," remains valid and portable, so the code above runs unchanged on every LTR.",[45,1379,1381],{"id":1380},"troubleshooting","Troubleshooting",[50,1383,1384,1399,1410,1420,1438],{},[53,1385,1386,1395,1396,1398],{},[60,1387,1388,1391,1392,74],{},[18,1389,1390],{},"geom.area()"," returns tiny decimals (e.g. ",[18,1393,1394],{},"0.00042"," The layer is in a geographic CRS, so the result is square degrees. Reproject to a projected CRS, or use the ",[18,1397,461],{}," recipe above.",[53,1400,1401,1406,1407,109],{},[60,1402,1403,109],{},[18,1404,1405],{},"AttributeError: 'QgsVectorLayer' object has no attribute 'dataProvider'"," The object is not a vector layer (often a raster or a partially loaded layer). Confirm with ",[18,1408,1409],{},"from qgis.core import QgsMapLayer; layer.type() == QgsMapLayer.VectorLayer",[53,1411,1412,1415,1416,1419],{},[60,1413,1414],{},"Could not add field \u002F write refused."," The data source is read-only. Export to GeoPackage first with ",[18,1417,1418],{},"QgsVectorFileWriter",", or use the field-calculator algorithm, which writes a new layer.",[53,1421,1422,1428,1429,1432,1433,1359,1435,1437],{},[60,1423,1424,30,1426,34],{},[18,1425,29],{},[18,1427,33],{}," Invalid topology — self-intersecting rings or collapsed geometries. Run ",[18,1430,1431],{},"processing.run(\"native:fixgeometries\", {\"INPUT\": layer, \"OUTPUT\": \"memory:\"})"," first, and keep the ",[18,1434,446],{},[18,1436,449],{}," guards so bad features are skipped rather than corrupting the column.",[53,1439,1440,1443,1444,1447],{},[60,1441,1442],{},"Values look plausible but wrong."," Verify against a polygon of known size or the QGIS Identify tool. In production, wrap the loop in ",[18,1445,1446],{},"try\u002Fexcept"," and log failing feature IDs so one bad geometry does not abort the whole run.",[45,1449,1451],{"id":1450},"conclusion","Conclusion",[14,1453,1454,1455,1457,1458,1460,1461,1463],{},"Calculating polygon areas in PyQGIS comes down to one decision: planar or geodesic. On a projected CRS, ",[18,1456,20],{}," inside an ",[18,1459,1317],{}," transaction is fast, exact, and only a few lines. On a geographic CRS, ",[18,1462,461],{}," with a WGS84 ellipsoid gives accurate square meters without touching the projection. Guard every geometry, batch writes into a single commit, and validate the output against a known reference — with those habits the same script scales cleanly from a single Console run to an automated batch pipeline.",[45,1465,1467],{"id":1466},"frequently-asked-questions","Frequently Asked Questions",[14,1469,1470,1476,1478,1479,1481],{},[60,1471,1472,1473,1475],{},"Why does ",[18,1474,1390],{}," return tiny decimal numbers for my WGS84 layer?",[18,1477,20],{}," is strictly planar and returns area in the layer's native CRS units. For a geographic CRS such as EPSG:4326 those units are degrees, so you get square degrees — meaningless as real-world area. Reproject the layer to a projected CRS such as UTM, or use the ",[18,1480,461],{}," geodesic recipe shown above.",[14,1483,1484,1487,1488,1490,1491,1493],{},[60,1485,1486],{},"What units does the calculated area come out in?","\nWith planar ",[18,1489,1390],{}," the units follow the CRS: a metric projected CRS yields square meters, a CRS in feet yields square feet. Divide square meters by 10,000 for hectares or by 1,000,000 for square kilometers. The ",[18,1492,461],{}," approach returns square meters when you set a metric ellipsoid like WGS84.",[14,1495,1496,1499,1500,1502,1503,96,1506,1508,1509,1512],{},[60,1497,1498],{},"How do I get accurate areas without changing my layer's CRS?","\nUse ",[18,1501,461],{}," with ",[18,1504,1505],{},"setSourceCrs()",[18,1507,572],{},", then call ",[18,1510,1511],{},"da.measureArea(geom)",". This ellipsoidal calculation stays accurate regardless of the layer's stored projection, so you never have to physically reproject the data.",[14,1514,1515,1523,1524,1527,1528,96,1530,1532],{},[60,1516,1517,1518,30,1520,1522],{},"Why are some of my area values ",[18,1519,29],{},[18,1521,33],{},"?","\nThese almost always come from invalid topology — self-intersecting rings, collapsed geometries, or empty features. Run ",[18,1525,1526],{},"native:fixgeometries"," before calculating and keep the ",[18,1529,446],{},[18,1531,449],{}," guards in the loop so problem features are skipped rather than writing garbage into the field.",[14,1534,1535,1538,1539,1541,1542,1545],{},[60,1536,1537],{},"Can I calculate areas without writing a script?","\nYes. Use the native field-calculator algorithm with the ",[18,1540,941],{}," expression, either through ",[18,1543,1544],{},"processing.run(\"native:fieldcalculator\", ...)"," as shown above or via the Processing Toolbox GUI. It avoids provider locks by writing to a fresh output layer, which is often safer for batch workflows.",[45,1547,1549],{"id":1548},"related","Related",[50,1551,1552,1557,1563,1568],{},[53,1553,1554],{},[39,1555,1556],{"href":41},"Vector Data Manipulation in PyQGIS",[53,1558,1559],{},[39,1560,1562],{"href":1561},"\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Fclip-vector-layer-pyqgis\u002F","Clip a Vector Layer in PyQGIS",[53,1564,1565],{},[39,1566,1567],{"href":953},"Merge Multiple Shapefiles in PyQGIS",[53,1569,1570],{},[39,1571,1572],{"href":465},"Coordinate Reference Systems in PyQGIS",[1574,1575,1576],"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 .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}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);}",{"title":131,"searchDepth":155,"depth":155,"links":1578},[1579,1580,1585,1586,1587,1588,1589,1590,1591],{"id":47,"depth":155,"text":48},{"id":112,"depth":155,"text":113,"children":1581},[1582,1583,1584],{"id":454,"depth":168,"text":455},{"id":780,"depth":168,"text":781},{"id":958,"depth":168,"text":959},{"id":1096,"depth":155,"text":1097},{"id":1182,"depth":155,"text":1183},{"id":1273,"depth":155,"text":1274},{"id":1380,"depth":155,"text":1381},{"id":1450,"depth":155,"text":1451},{"id":1466,"depth":155,"text":1467},{"id":1548,"depth":155,"text":1549},"Calculate polygon areas with PyQGIS using QgsGeometry.area and QgsDistanceArea. Handle CRS-aware measurements, add area fields, and process layers efficiently.","md",{"slug":12,"type":1595,"breadcrumb":1596,"datePublished":1597,"dateModified":1598},"article","Calculate Polygon Areas","2025-05-28","2026-07-18","\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Fpyqgis-script-to-calculate-polygon-areas",{"title":5,"description":1592},"spatial-data-processing-automation\u002Fvector-data-manipulation\u002Fpyqgis-script-to-calculate-polygon-areas\u002Findex","qzOsy_ZtLFzyPaAtPeQK6Oe7zJqANhMvYysTiiFpd8k",1787823364862]