[{"data":1,"prerenderedAt":1836},["ShallowReactive",2],{"doc:\u002Fpyqgis-fundamentals-environment-setup\u002Ffeatures-geometries-and-memory-layers":3},{"id":4,"title":5,"body":6,"description":1825,"extension":1826,"meta":1827,"navigation":341,"path":1832,"seo":1833,"stem":1834,"__hash__":1835},"docs\u002Fpyqgis-fundamentals-environment-setup\u002Ffeatures-geometries-and-memory-layers\u002Findex.md","Features, Geometries & Memory Layers in PyQGIS",{"type":7,"value":8,"toc":1810},"minimark",[9,13,30,39,207,212,215,291,295,309,482,515,531,535,556,647,690,761,781,785,791,853,872,876,887,967,1000,1007,1111,1115,1118,1134,1157,1244,1266,1270,1287,1291,1302,1305,1479,1488,1492,1495,1554,1562,1566,1569,1635,1638,1642,1674,1678,1702,1708,1721,1727,1733,1737,1806],[10,11,5],"h1",{"id":12},"features-geometries-memory-layers-in-pyqgis",[14,15,16,17,21,22,25,26,29],"p",{},"Every PyQGIS task, from a one-line console experiment to a plugin with a dozen dialogs, eventually comes down to the same three objects: a ",[18,19,20],"code",{},"QgsFeature"," holding attribute values, the ",[18,23,24],{},"QgsFields"," that describe those values, and a ",[18,27,28],{},"QgsGeometry"," holding a shape. Layers, providers, Processing and the map canvas are all machinery for moving those three objects around. Knowing them well — what they contain, how they are created, how they fail — removes a whole class of bugs that otherwise show up later as wrong numbers, missing features or crashes.",[14,31,32,33,38],{},"This guide belongs to ",[34,35,37],"a",{"href":36},"\u002Fpyqgis-fundamentals-environment-setup\u002F","PyQGIS Fundamentals & Environment Setup",". It is aimed at anyone who has run a few scripts and wants to understand the data model properly: analysts automating their own work, developers starting on plugins, and people moving from GeoPandas or ArcPy who need to know where the familiar concepts live in QGIS. It explains the model, then links to focused recipes for each task.",[14,40,41],{},[42,43,48,52,56,63,80,89,98,104,109,113,117,121,126,133,136,144,148,153,157,161,166,169,173,177,181,187,190,193,196,199,203],"svg",{"viewBox":44,"role":45,"ariaLabel":46,"xmlns":47},"0 0 760 320","img","A vector layer with its fields, geometry type and CRS delivering features, each holding an id, attribute values in field order and one geometry without its own CRS","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[49,50,51],"title",{},"Layer, feature, fields and geometry",[53,54,55],"desc",{},"A vector layer has one QgsFields object describing named, typed columns, one geometry type and one CRS. It delivers features through a data provider. Each QgsFeature has an id, a list of attribute values in field order and one QgsGeometry. The geometry has a type, such as MultiPolygon with Z, and coordinates but no CRS of its own. Memory layers keep features in RAM; file and database layers keep them in a data source.",[57,58],"rect",{"x":59,"y":59,"width":60,"height":61,"fill":62},"0","760","320","#f6f3ea",[64,65,66],"defs",{},[67,68,75],"marker",{"id":69,"viewBox":70,"refX":71,"refY":72,"markerWidth":73,"markerHeight":73,"orient":74},"fgmModelArrow","0 0 10 10","8","5","7","auto-start-reverse",[76,77],"path",{"d":78,"fill":79},"M0 0 L10 5 L0 10 z","#2f3b35",[81,82,88],"text",{"x":83,"y":84,"style":85,"fill":86,"textAnchor":87},"380","28","text-anchor:middle;font-size:14px;font-family:sans-serif;font-weight:bold","#17211d","middle","The objects every script works with",[57,90],{"x":91,"y":92,"width":93,"height":94,"rx":71,"fill":95,"stroke":96,"style":97},"24","52","260","250","#eff3ff","#2563eb","stroke-width:2",[81,99,103],{"x":100,"y":101,"style":102,"fill":96,"textAnchor":87},"154","115.78","text-anchor:middle;font-size:11.5px;font-family:sans-serif;font-weight:bold","QgsVectorLayer",[81,105,108],{"x":100,"y":106,"style":107,"fill":79,"textAnchor":87},"141.78","text-anchor:middle;font-size:10.5px;font-family:sans-serif","QgsFields — the schema",[81,110,112],{"x":100,"y":111,"style":107,"fill":79,"textAnchor":87},"167.78","geometry type",[81,114,116],{"x":100,"y":115,"style":107,"fill":79,"textAnchor":87},"193.78","CRS",[81,118,120],{"x":100,"y":119,"style":107,"fill":79,"textAnchor":87},"219.78","data provider",[81,122,125],{"x":100,"y":123,"style":107,"fill":124,"textAnchor":87},"245.78","#59645f","memory, ogr, postgres…",[127,128],"line",{"x1":129,"y1":130,"x2":131,"y2":130,"stroke":79,"style":132},"284","130","330","stroke-width:1.8;marker-end:url(#fgmModelArrow)",[127,134],{"x1":129,"y1":135,"x2":131,"y2":135,"stroke":79,"style":132},"220",[57,137],{"x":138,"y":139,"width":140,"height":141,"rx":71,"fill":142,"stroke":143,"style":97},"338","60","186","140","#eef7f4","#0f766e",[81,145,20],{"x":146,"y":147,"style":102,"fill":143,"textAnchor":87},"431","94.78",[81,149,152],{"x":146,"y":150,"style":151,"fill":79,"textAnchor":87},"120.78","text-anchor:middle;font-size:10.0px;font-family:monospace","id()",[81,154,156],{"x":146,"y":155,"style":151,"fill":79,"textAnchor":87},"146.78","attributes()",[81,158,160],{"x":146,"y":159,"style":151,"fill":79,"textAnchor":87},"172.78","geometry()",[57,162],{"x":138,"y":163,"width":140,"height":164,"rx":71,"fill":165,"stroke":124,"style":97},"216","86","#fffdf7",[81,167,24],{"x":146,"y":168,"style":102,"fill":86,"textAnchor":87},"242.83",[81,170,172],{"x":146,"y":171,"style":107,"fill":124,"textAnchor":87},"262.78","name, type, length",[81,174,176],{"x":146,"y":175,"style":107,"fill":124,"textAnchor":87},"282.73","same order as values",[127,178],{"x1":179,"y1":130,"x2":180,"y2":130,"stroke":79,"style":132},"524","566",[57,182],{"x":183,"y":139,"width":184,"height":141,"rx":71,"fill":185,"stroke":186,"style":97},"574","162","#fdf2e2","#b45309",[81,188,28],{"x":189,"y":147,"style":102,"fill":186,"textAnchor":87},"655",[81,191,192],{"x":189,"y":150,"style":107,"fill":79,"textAnchor":87},"type + coordinates",[81,194,195],{"x":189,"y":155,"style":107,"fill":124,"textAnchor":87},"no CRS inside",[81,197,198],{"x":189,"y":159,"style":107,"fill":124,"textAnchor":87},"Z, M optional",[81,200,202],{"x":189,"y":201,"style":107,"fill":124,"textAnchor":87},"240","CRS comes from the layer",[81,204,206],{"x":189,"y":205,"style":107,"fill":124,"textAnchor":87},"262","values follow field order",[208,209,211],"h2",{"id":210},"what-this-guide-covers","What this guide covers",[14,213,214],{},"The recipes below follow the order in which most scripts touch the data model:",[216,217,218,231,241,251,261,271,281],"ul",{},[219,220,221,225,226,230],"li",{},[222,223,224],"strong",{},"Make a layer to hold results"," — ",[34,227,229],{"href":228},"\u002Fpyqgis-fundamentals-environment-setup\u002Ffeatures-geometries-and-memory-layers\u002Fcreate-memory-layer-pyqgis\u002F","create a memory layer"," with a complete URI, a cloned schema or a filtered snapshot.",[219,232,233,225,236,240],{},[222,234,235],{},"Make geometry from outside data",[34,237,239],{"href":238},"\u002Fpyqgis-fundamentals-environment-setup\u002Ffeatures-geometries-and-memory-layers\u002Fcreate-geometry-from-wkt-and-geojson-pyqgis\u002F","create geometry from WKT and GeoJSON",", WKB and coordinate lists, with checks that catch bad input at the source row.",[219,242,243,225,246,250],{},[222,244,245],{},"Put features into layers",[34,247,249],{"href":248},"\u002Fpyqgis-fundamentals-environment-setup\u002Ffeatures-geometries-and-memory-layers\u002Fadd-features-to-vector-layer-pyqgis\u002F","add features to a vector layer"," through the provider or the edit buffer, honouring defaults and constraints.",[219,252,253,225,256,260],{},[222,254,255],{},"Get features out",[34,257,259],{"href":258},"\u002Fpyqgis-fundamentals-environment-setup\u002Ffeatures-geometries-and-memory-layers\u002Fread-feature-attributes-and-geometry-pyqgis\u002F","read feature attributes and geometry"," efficiently, in order, and without iterator pitfalls.",[219,262,263,225,266,270],{},[222,264,265],{},"Deal with empty values",[34,267,269],{"href":268},"\u002Fpyqgis-fundamentals-environment-setup\u002Ffeatures-geometries-and-memory-layers\u002Fhandle-null-values-and-qvariant-pyqgis\u002F","handle NULL values and QVariant"," on QGIS 3 and QGIS 4.",[219,272,273,225,276,280],{},[222,274,275],{},"Change data safely",[34,277,279],{"href":278},"\u002Fpyqgis-fundamentals-environment-setup\u002Ffeatures-geometries-and-memory-layers\u002Fundo-edits-with-edit-buffer-pyqgis\u002F","undo edits with the edit buffer",", group changes into undo steps and handle failed commits.",[219,282,283,225,286,290],{},[222,284,285],{},"Reshape geometry to fit",[34,287,289],{"href":288},"\u002Fpyqgis-fundamentals-environment-setup\u002Ffeatures-geometries-and-memory-layers\u002Fconvert-geometry-type-pyqgis\u002F","convert geometry types"," between single and multi, polygons and lines, curves and segments, 2D and 3D.",[208,292,294],{"id":293},"fields-the-schema-every-feature-follows","Fields: the schema every feature follows",[14,296,297,298,301,302,305,306,308],{},"A layer's fields are its columns: each ",[18,299,300],{},"QgsField"," has a name, a type, and optionally a length, precision, alias and comment. ",[18,303,304],{},"layer.fields()"," returns them as a ",[18,307,24],{}," collection, and every feature's attribute list follows that collection's order exactly. This is the single most important rule of the data model: attributes are positional, and the positions are defined by the layer.",[310,311,316],"pre",{"className":312,"code":313,"language":314,"meta":315,"style":315},"language-python shiki shiki-themes github-dark","from qgis.core import QgsProject\n\nlayer = QgsProject.instance().mapLayersByName(\"hydrants\")[0]\nfor i, field in enumerate(layer.fields()):\n    origin = layer.fields().fieldOrigin(i)\n    print(f\"{i:>2} {field.name():\u003C16} {field.typeName():\u003C10} \"\n          f\"len={field.length():\u003C4} origin={origin}\")\n","python","",[18,317,318,336,343,368,386,397,448],{"__ignoreMap":315},[319,320,322,326,330,333],"span",{"class":127,"line":321},1,[319,323,325],{"class":324},"snl16","from",[319,327,329],{"class":328},"s95oV"," qgis.core ",[319,331,332],{"class":324},"import",[319,334,335],{"class":328}," QgsProject\n",[319,337,339],{"class":127,"line":338},2,[319,340,342],{"emptyLinePlaceholder":341},true,"\n",[319,344,346,349,352,355,359,362,365],{"class":127,"line":345},3,[319,347,348],{"class":328},"layer ",[319,350,351],{"class":324},"=",[319,353,354],{"class":328}," QgsProject.instance().mapLayersByName(",[319,356,358],{"class":357},"sU2Wk","\"hydrants\"",[319,360,361],{"class":328},")[",[319,363,59],{"class":364},"sDLfK",[319,366,367],{"class":328},"]\n",[319,369,371,374,377,380,383],{"class":127,"line":370},4,[319,372,373],{"class":324},"for",[319,375,376],{"class":328}," i, field ",[319,378,379],{"class":324},"in",[319,381,382],{"class":364}," enumerate",[319,384,385],{"class":328},"(layer.fields()):\n",[319,387,389,392,394],{"class":127,"line":388},5,[319,390,391],{"class":328},"    origin ",[319,393,351],{"class":324},[319,395,396],{"class":328}," layer.fields().fieldOrigin(i)\n",[319,398,400,403,406,409,412,415,418,421,424,427,430,433,435,437,440,443,445],{"class":127,"line":399},6,[319,401,402],{"class":364},"    print",[319,404,405],{"class":328},"(",[319,407,408],{"class":324},"f",[319,410,411],{"class":357},"\"",[319,413,414],{"class":364},"{",[319,416,417],{"class":328},"i",[319,419,420],{"class":324},":>2",[319,422,423],{"class":364},"}",[319,425,426],{"class":364}," {",[319,428,429],{"class":328},"field.name()",[319,431,432],{"class":324},":\u003C16",[319,434,423],{"class":364},[319,436,426],{"class":364},[319,438,439],{"class":328},"field.typeName()",[319,441,442],{"class":324},":\u003C10",[319,444,423],{"class":364},[319,446,447],{"class":357}," \"\n",[319,449,451,454,457,459,462,465,467,470,472,475,477,479],{"class":127,"line":450},7,[319,452,453],{"class":324},"          f",[319,455,456],{"class":357},"\"len=",[319,458,414],{"class":364},[319,460,461],{"class":328},"field.length()",[319,463,464],{"class":324},":\u003C4",[319,466,423],{"class":364},[319,468,469],{"class":357}," origin=",[319,471,414],{"class":364},[319,473,474],{"class":328},"origin",[319,476,423],{"class":364},[319,478,411],{"class":357},[319,480,481],{"class":328},")\n",[14,483,484,487,488,491,492,495,496,495,499,502,503,506,507,510,511,514],{},[222,485,486],{},"Breakdown:"," ",[18,489,490],{},"typeName()"," is the provider's own type name (",[18,493,494],{},"Integer64",", ",[18,497,498],{},"varchar",[18,500,501],{},"Real","), while ",[18,504,505],{},"type()"," is the Qt type QGIS uses internally. The field origin distinguishes columns stored in the data source from joined fields and virtual fields computed by expression; joined and expression fields appear in ",[18,508,509],{},"fields()"," but cannot be written through the provider. Primary key columns such as a GeoPackage ",[18,512,513],{},"fid"," appear as ordinary fields and are usually filled by the data source.",[14,516,517,518,521,522,526,527,530],{},"Because order matters, creating a feature from the layer's fields — ",[18,519,520],{},"QgsFeature(layer.fields())"," — is the habit that prevents most attribute bugs. It produces a feature with the right number of NULL slots, and lets you assign values by name. Adding or removing fields changes the positions of every later column, which is why code that caches field indexes must refresh them after any schema change; ",[34,523,525],{"href":524},"\u002Fspatial-data-processing-automation\u002Fattribute-tables-and-field-management\u002Fadd-field-to-layer-pyqgis\u002F","adding fields to a layer"," shows the ",[18,528,529],{},"updateFields()"," step that keeps the layer and its provider in sync.",[208,532,534],{"id":533},"geometry-shapes-without-a-coordinate-system","Geometry: shapes without a coordinate system",[14,536,537,538,540,541,495,544,495,547,550,551,555],{},"A ",[18,539,28],{}," is a shape and nothing more. It knows its type — ",[18,542,543],{},"Point",[18,545,546],{},"LineString",[18,548,549],{},"Polygon",", their multipart and curved variants, each with optional Z and M — and its coordinates. It does not know what those coordinates mean. The CRS belongs to the layer, and moving a geometry between layers in different systems requires an explicit ",[34,552,554],{"href":553},"\u002Fspatial-data-processing-automation\u002Fcoordinate-reference-systems\u002Ftransform-point-coordinates-pyqgis\u002F","coordinate transform",".",[14,557,558],{},[42,559,562,565,568,571,574,578,583,587,590,594,600,602,606,609,612,615,617,623,629,633,637,643],{"viewBox":560,"role":45,"ariaLabel":561,"xmlns":47},"0 0 760 280","A geometry type is made of a family, a part structure with curved variants, and dimensions; a layer declares one type and incoming geometry must match its family",[49,563,564],{},"Geometry type is three choices",[53,566,567],{},"A geometry type combines a family, point, line or polygon; a part structure, single or multi, plus curved variants such as CircularString and CurvePolygon; and dimensions, 2D, with Z, with M, or with both. MultiPolygonZ is polygon family, multipart, with Z. A layer has one declared type, and geometries added to it must match its family, while part structure and dimensions are often converted automatically or with one call.",[57,569],{"x":59,"y":59,"width":60,"height":570,"fill":62},"280",[81,572,573],{"x":83,"y":84,"style":85,"fill":86,"textAnchor":87},"Family × parts × dimensions",[57,575],{"x":91,"y":576,"width":135,"height":577,"rx":71,"fill":185,"stroke":186,"style":97},"56","150",[81,579,582],{"x":580,"y":581,"style":102,"fill":186,"textAnchor":87},"134","95.78","family",[81,584,586],{"x":580,"y":585,"style":107,"fill":79,"textAnchor":87},"121.78","point",[81,588,127],{"x":580,"y":589,"style":107,"fill":79,"textAnchor":87},"147.78",[81,591,593],{"x":580,"y":592,"style":107,"fill":79,"textAnchor":87},"173.78","polygon",[81,595,599],{"x":596,"y":597,"style":598,"fill":124,"textAnchor":87},"264","136","text-anchor:middle;font-size:20.0px;font-family:sans-serif","×",[57,601],{"x":129,"y":576,"width":135,"height":577,"rx":71,"fill":95,"stroke":96,"style":97},[81,603,605],{"x":604,"y":581,"style":102,"fill":96,"textAnchor":87},"394","parts",[81,607,608],{"x":604,"y":585,"style":107,"fill":79,"textAnchor":87},"single",[81,610,611],{"x":604,"y":589,"style":107,"fill":79,"textAnchor":87},"multi",[81,613,614],{"x":604,"y":592,"style":107,"fill":124,"textAnchor":87},"curved variants",[81,616,599],{"x":179,"y":597,"style":598,"fill":124,"textAnchor":87},[57,618],{"x":619,"y":576,"width":620,"height":577,"rx":71,"fill":621,"stroke":622,"style":97},"544","192","#e8efe6","#15803d",[81,624,628],{"x":625,"y":626,"style":102,"fill":627,"textAnchor":87},"640","104.78","#166534","dimensions",[81,630,632],{"x":625,"y":631,"style":107,"fill":79,"textAnchor":87},"134.78","2D",[81,634,636],{"x":625,"y":635,"style":107,"fill":79,"textAnchor":87},"164.78","Z · M · ZM",[57,638],{"x":639,"y":640,"width":641,"height":642,"rx":71,"fill":165,"stroke":124,"style":97},"180","226","400","40",[81,644,646],{"x":83,"y":645,"style":151,"fill":79,"textAnchor":87},"249.78","MultiPolygonZ = polygon × multi × Z",[14,648,649,651,652,655,656,495,659,495,662,665,666,495,669,495,672,495,675,678,679,682,683,686,687,689],{},[18,650,28],{}," is a wrapper around an abstract geometry object (",[18,653,654],{},"QgsAbstractGeometry"," subclasses such as ",[18,657,658],{},"QgsPoint",[18,660,661],{},"QgsLineString",[18,663,664],{},"QgsPolygon","). Most everyday work happens on the wrapper — ",[18,667,668],{},"area()",[18,670,671],{},"buffer()",[18,673,674],{},"intersects()",[18,676,677],{},"asWkt()"," — while fine-grained work such as adding a vertex or dropping a Z value goes through ",[18,680,681],{},"constGet()"," for reading and ",[18,684,685],{},"get()"," for modifying the underlying object. The wrapper is implicitly shared: copying a ",[18,688,28],{}," is cheap, and the data is duplicated only when one copy is modified.",[310,691,693],{"className":312,"code":692,"language":314,"meta":315,"style":315},"from qgis.core import QgsGeometry, QgsWkbTypes\n\ng = QgsGeometry.fromWkt(\"MultiPolygon Z (((0 0 5, 10 0 5, 10 10 6, 0 10 6, 0 0 5)))\")\nt = g.wkbType()\nprint(QgsWkbTypes.displayString(t))                 # MultiPolygonZ\nprint(QgsWkbTypes.geometryType(t), QgsWkbTypes.isMultiType(t), QgsWkbTypes.hasZ(t))\nprint(g.area(), g.isGeosValid(), g.constGet().partCount())\n",[18,694,695,706,710,725,735,747,754],{"__ignoreMap":315},[319,696,697,699,701,703],{"class":127,"line":321},[319,698,325],{"class":324},[319,700,329],{"class":328},[319,702,332],{"class":324},[319,704,705],{"class":328}," QgsGeometry, QgsWkbTypes\n",[319,707,708],{"class":127,"line":338},[319,709,342],{"emptyLinePlaceholder":341},[319,711,712,715,717,720,723],{"class":127,"line":345},[319,713,714],{"class":328},"g ",[319,716,351],{"class":324},[319,718,719],{"class":328}," QgsGeometry.fromWkt(",[319,721,722],{"class":357},"\"MultiPolygon Z (((0 0 5, 10 0 5, 10 10 6, 0 10 6, 0 0 5)))\"",[319,724,481],{"class":328},[319,726,727,730,732],{"class":127,"line":370},[319,728,729],{"class":328},"t ",[319,731,351],{"class":324},[319,733,734],{"class":328}," g.wkbType()\n",[319,736,737,740,743],{"class":127,"line":388},[319,738,739],{"class":364},"print",[319,741,742],{"class":328},"(QgsWkbTypes.displayString(t))                 ",[319,744,746],{"class":745},"sjoCn","# MultiPolygonZ\n",[319,748,749,751],{"class":127,"line":399},[319,750,739],{"class":364},[319,752,753],{"class":328},"(QgsWkbTypes.geometryType(t), QgsWkbTypes.isMultiType(t), QgsWkbTypes.hasZ(t))\n",[319,755,756,758],{"class":127,"line":450},[319,757,739],{"class":364},[319,759,760],{"class":328},"(g.area(), g.isGeosValid(), g.constGet().partCount())\n",[14,762,763,487,765,768,769,772,773,776,777,780],{},[222,764,486],{},[18,766,767],{},"QgsWkbTypes"," answers questions about a type value without needing a geometry — useful for checking a layer's declared type before writing. ",[18,770,771],{},"isGeosValid()"," checks the OGC validity rules that GEOS-based operations rely on; an invalid polygon can still be drawn, but buffers, intersections and areas computed from it may be wrong. ",[34,774,775],{"href":238},"Creating geometry from WKT and GeoJSON"," shows where to put those checks, and ",[34,778,779],{"href":288},"converting geometry types"," covers moving between types when a target layer demands it.",[208,782,784],{"id":783},"memory-layers-a-scratch-surface-with-the-full-layer-api","Memory layers: a scratch surface with the full layer API",[14,786,787,788,790],{},"A memory layer is a ",[18,789,103],{}," whose provider keeps features in RAM. It needs no file, no path and no cleanup, and it supports everything a file-backed layer does: styling, labelling, editing, expressions, spatial indexing and Processing. That makes it the default container for anything a script creates — intermediate results, records fetched from an API, features generated from calculations.",[310,792,794],{"className":312,"code":793,"language":314,"meta":315,"style":315},"from qgis.core import QgsVectorLayer, QgsProject\n\nresults = QgsVectorLayer(\n    \"Polygon?crs=EPSG:25832&field=zone:string(20)&field=score:double&index=yes\",\n    \"suitability (scratch)\", \"memory\")\nQgsProject.instance().addMapLayer(results)\nprint(results.isValid(), results.fields().names())\n",[18,795,796,807,811,821,829,841,846],{"__ignoreMap":315},[319,797,798,800,802,804],{"class":127,"line":321},[319,799,325],{"class":324},[319,801,329],{"class":328},[319,803,332],{"class":324},[319,805,806],{"class":328}," QgsVectorLayer, QgsProject\n",[319,808,809],{"class":127,"line":338},[319,810,342],{"emptyLinePlaceholder":341},[319,812,813,816,818],{"class":127,"line":345},[319,814,815],{"class":328},"results ",[319,817,351],{"class":324},[319,819,820],{"class":328}," QgsVectorLayer(\n",[319,822,823,826],{"class":127,"line":370},[319,824,825],{"class":357},"    \"Polygon?crs=EPSG:25832&field=zone:string(20)&field=score:double&index=yes\"",[319,827,828],{"class":328},",\n",[319,830,831,834,836,839],{"class":127,"line":388},[319,832,833],{"class":357},"    \"suitability (scratch)\"",[319,835,495],{"class":328},[319,837,838],{"class":357},"\"memory\"",[319,840,481],{"class":328},[319,842,843],{"class":127,"line":399},[319,844,845],{"class":328},"QgsProject.instance().addMapLayer(results)\n",[319,847,848,850],{"class":127,"line":450},[319,849,739],{"class":364},[319,851,852],{"class":328},"(results.isValid(), results.fields().names())\n",[14,854,855,856,859,860,863,864,526,868,871],{},"The URI declares the geometry type, CRS and fields in one string, so the layer is complete the moment it exists. The two things to remember are that memory layers vanish when QGIS closes — save anything worth keeping with ",[18,857,858],{},"native:savefeatures"," — and that Python can lose them earlier if nothing holds a reference. ",[34,861,862],{"href":228},"Creating a memory layer"," covers both, plus cloning a schema from an existing layer and materialising a filtered copy in one call. Processing uses memory layers for the same purpose; ",[34,865,867],{"href":866},"\u002Fspatial-data-processing-automation\u002Fchaining-processing-algorithms\u002Fuse-memory-layers-between-algorithms-pyqgis\u002F","passing memory layers between algorithms",[18,869,870],{},"memory:"," output shortcut.",[208,873,875],{"id":874},"reading-features-requests-iterators-and-null","Reading features: requests, iterators and NULL",[14,877,878,879,882,883,886],{},"Features come out of a layer through ",[18,880,881],{},"getFeatures()",", optionally with a ",[18,884,885],{},"QgsFeatureRequest"," describing which features and which columns are wanted. The request is passed down to the provider, which on a database becomes part of the SQL query — so a request that filters rows, trims columns and skips geometry can be dramatically faster than reading everything and filtering in Python.",[14,888,889],{},[42,890,893,896,899,901,904,907,912,915,918,922,925,930,933,937,940,944,947,950,953,957,960,963],{"viewBox":891,"role":45,"ariaLabel":892,"xmlns":47},"0 0 760 260","Feature request controls for rows, columns, geometry, order and limit, each pushed down to the data provider",[49,894,895],{},"A request narrows what the provider reads",[53,897,898],{},"Three narrowing controls on a feature request: rows with setFilterExpression, setFilterRect or setFilterFids; columns with setSubsetOfAttributes; and geometry with the NoGeometry flag. Order and limit add addOrderBy and setLimit. Each control is pushed down to the data provider where possible, so less data is read, decoded and transferred.",[57,900],{"x":59,"y":59,"width":60,"height":93,"fill":62},[81,902,903],{"x":83,"y":84,"style":85,"fill":86,"textAnchor":87},"Ask for less, read less",[57,905],{"x":91,"y":576,"width":906,"height":577,"rx":71,"fill":142,"stroke":143,"style":97},"170",[81,908,911],{"x":909,"y":910,"style":102,"fill":143,"textAnchor":87},"109","92.78","rows",[81,913,914],{"x":909,"y":150,"style":151,"fill":79,"textAnchor":87},"expression",[81,916,57],{"x":909,"y":917,"style":151,"fill":79,"textAnchor":87},"148.78",[81,919,921],{"x":909,"y":920,"style":151,"fill":79,"textAnchor":87},"176.78","fids",[57,923],{"x":924,"y":576,"width":906,"height":577,"rx":71,"fill":95,"stroke":96,"style":97},"210",[81,926,929],{"x":927,"y":928,"style":102,"fill":96,"textAnchor":87},"295","106.78","columns",[81,931,932],{"x":927,"y":631,"style":151,"fill":79,"textAnchor":87},"subset of",[81,934,936],{"x":927,"y":935,"style":151,"fill":79,"textAnchor":87},"162.78","attributes",[57,938],{"x":939,"y":576,"width":906,"height":577,"rx":71,"fill":185,"stroke":186,"style":97},"396",[81,941,943],{"x":942,"y":928,"style":102,"fill":186,"textAnchor":87},"481","geometry",[81,945,946],{"x":942,"y":631,"style":151,"fill":79,"textAnchor":87},"NoGeometry",[81,948,949],{"x":942,"y":935,"style":107,"fill":124,"textAnchor":87},"skip decoding",[57,951],{"x":952,"y":576,"width":100,"height":577,"rx":71,"fill":621,"stroke":622,"style":97},"582",[81,954,956],{"x":955,"y":928,"style":102,"fill":627,"textAnchor":87},"659","order, limit",[81,958,959],{"x":955,"y":631,"style":151,"fill":79,"textAnchor":87},"addOrderBy",[81,961,962],{"x":955,"y":935,"style":151,"fill":79,"textAnchor":87},"setLimit",[81,964,966],{"x":83,"y":965,"style":107,"fill":124,"textAnchor":87},"236","all pushed down to the provider where it can — SQL on databases",[14,968,969,970,973,974,977,978,981,982,984,985,988,989,992,993,996,997,555],{},"Two things trip up nearly everyone at some point. The first is NULL: on QGIS 3 an empty attribute is a ",[18,971,972],{},"QVariant"," that prints as ",[18,975,976],{},"NULL",", is falsy, and is not equal to ",[18,979,980],{},"None","; on QGIS 4 it is ",[18,983,980],{},". Tests written as ",[18,986,987],{},"value is None"," silently treat every empty value as present on QGIS 3. ",[34,990,991],{"href":268},"Handling NULL values and QVariant"," gives a helper that works on both versions and covers dates, which arrive as ",[18,994,995],{},"QDate"," and ",[18,998,999],{},"QDateTime",[14,1001,1002,1003,1006],{},"The second is modifying a layer while iterating it. The iterator holds an open cursor on the data source; deleting or editing features underneath it gives undefined results. Collect ids or changes during the loop and apply them afterwards. ",[34,1004,1005],{"href":258},"Reading feature attributes and geometry"," walks through attribute access by name and index, geometry accessors for each type, ordered and paged reads, and both pitfalls.",[310,1008,1010],{"className":312,"code":1009,"language":314,"meta":315,"style":315},"from qgis.core import QgsFeatureRequest\n\nreq = (QgsFeatureRequest()\n       .setFilterExpression('\"pressure_bar\" IS NULL OR \"pressure_bar\" \u003C 2.5')\n       .setSubsetOfAttributes([\"hydrant_id\", \"pressure_bar\"], layer.fields())\n       .setFlags(QgsFeatureRequest.NoGeometry))\nlow = [f[\"hydrant_id\"] for f in layer.getFeatures(req)]\nprint(len(low), \"hydrants with low or unknown pressure\")\n",[18,1011,1012,1023,1027,1037,1047,1063,1068,1093],{"__ignoreMap":315},[319,1013,1014,1016,1018,1020],{"class":127,"line":321},[319,1015,325],{"class":324},[319,1017,329],{"class":328},[319,1019,332],{"class":324},[319,1021,1022],{"class":328}," QgsFeatureRequest\n",[319,1024,1025],{"class":127,"line":338},[319,1026,342],{"emptyLinePlaceholder":341},[319,1028,1029,1032,1034],{"class":127,"line":345},[319,1030,1031],{"class":328},"req ",[319,1033,351],{"class":324},[319,1035,1036],{"class":328}," (QgsFeatureRequest()\n",[319,1038,1039,1042,1045],{"class":127,"line":370},[319,1040,1041],{"class":328},"       .setFilterExpression(",[319,1043,1044],{"class":357},"'\"pressure_bar\" IS NULL OR \"pressure_bar\" \u003C 2.5'",[319,1046,481],{"class":328},[319,1048,1049,1052,1055,1057,1060],{"class":127,"line":388},[319,1050,1051],{"class":328},"       .setSubsetOfAttributes([",[319,1053,1054],{"class":357},"\"hydrant_id\"",[319,1056,495],{"class":328},[319,1058,1059],{"class":357},"\"pressure_bar\"",[319,1061,1062],{"class":328},"], layer.fields())\n",[319,1064,1065],{"class":127,"line":399},[319,1066,1067],{"class":328},"       .setFlags(QgsFeatureRequest.NoGeometry))\n",[319,1069,1070,1073,1075,1078,1080,1083,1085,1088,1090],{"class":127,"line":450},[319,1071,1072],{"class":328},"low ",[319,1074,351],{"class":324},[319,1076,1077],{"class":328}," [f[",[319,1079,1054],{"class":357},[319,1081,1082],{"class":328},"] ",[319,1084,373],{"class":324},[319,1086,1087],{"class":328}," f ",[319,1089,379],{"class":324},[319,1091,1092],{"class":328}," layer.getFeatures(req)]\n",[319,1094,1096,1098,1100,1103,1106,1109],{"class":127,"line":1095},8,[319,1097,739],{"class":364},[319,1099,405],{"class":328},[319,1101,1102],{"class":364},"len",[319,1104,1105],{"class":328},"(low), ",[319,1107,1108],{"class":357},"\"hydrants with low or unknown pressure\"",[319,1110,481],{"class":328},[208,1112,1114],{"id":1113},"writing-features-provider-or-edit-buffer","Writing features: provider or edit buffer",[14,1116,1117],{},"Changes reach a data source by one of two routes, and choosing the right one is mostly a question of who is in charge.",[14,1119,1120,1121,1123,1124,495,1127,495,1130,1133],{},"The ",[222,1122,120],{}," route — ",[18,1125,1126],{},"layer.dataProvider().addFeatures()",[18,1128,1129],{},"changeAttributeValues()",[18,1131,1132],{},"deleteFeatures()"," — writes immediately with no undo and no edit session. It is right for scripts that build or update layers on their own: imports, scheduled jobs, analysis outputs. It is fast, especially when features are added in batches of a few thousand.",[14,1135,1120,1136,1123,1139,1142,1143,495,1146,1149,1150,1142,1153,1156],{},[222,1137,1138],{},"edit buffer",[18,1140,1141],{},"startEditing()",", then ",[18,1144,1145],{},"addFeature()",[18,1147,1148],{},"changeAttributeValue()"," or ",[18,1151,1152],{},"deleteFeature()",[18,1154,1155],{},"commitChanges()"," — holds changes in memory, records them on an undo stack, emits signals the attribute table and other plugins rely on, and writes everything in one go at commit. It is right whenever a person is working with the layer at the same time, which covers almost every plugin.",[310,1158,1160],{"className":312,"code":1159,"language":314,"meta":315,"style":315},"from qgis.core import edit\n\nwith edit(layer):                         # start, then commit or roll back\n    idx = layer.fields().indexOf(\"pressure_bar\")\n    layer.beginEditCommand(\"Clear implausible pressures\")\n    for f in layer.getFeatures('\"pressure_bar\" > 20'):\n        layer.changeAttributeValue(f.id(), idx, None)\n    layer.endEditCommand()\n",[18,1161,1162,1173,1177,1188,1202,1212,1230,1239],{"__ignoreMap":315},[319,1163,1164,1166,1168,1170],{"class":127,"line":321},[319,1165,325],{"class":324},[319,1167,329],{"class":328},[319,1169,332],{"class":324},[319,1171,1172],{"class":328}," edit\n",[319,1174,1175],{"class":127,"line":338},[319,1176,342],{"emptyLinePlaceholder":341},[319,1178,1179,1182,1185],{"class":127,"line":345},[319,1180,1181],{"class":324},"with",[319,1183,1184],{"class":328}," edit(layer):                         ",[319,1186,1187],{"class":745},"# start, then commit or roll back\n",[319,1189,1190,1193,1195,1198,1200],{"class":127,"line":370},[319,1191,1192],{"class":328},"    idx ",[319,1194,351],{"class":324},[319,1196,1197],{"class":328}," layer.fields().indexOf(",[319,1199,1059],{"class":357},[319,1201,481],{"class":328},[319,1203,1204,1207,1210],{"class":127,"line":388},[319,1205,1206],{"class":328},"    layer.beginEditCommand(",[319,1208,1209],{"class":357},"\"Clear implausible pressures\"",[319,1211,481],{"class":328},[319,1213,1214,1217,1219,1221,1224,1227],{"class":127,"line":399},[319,1215,1216],{"class":324},"    for",[319,1218,1087],{"class":328},[319,1220,379],{"class":324},[319,1222,1223],{"class":328}," layer.getFeatures(",[319,1225,1226],{"class":357},"'\"pressure_bar\" > 20'",[319,1228,1229],{"class":328},"):\n",[319,1231,1232,1235,1237],{"class":127,"line":450},[319,1233,1234],{"class":328},"        layer.changeAttributeValue(f.id(), idx, ",[319,1236,980],{"class":364},[319,1238,481],{"class":328},[319,1240,1241],{"class":127,"line":1095},[319,1242,1243],{"class":328},"    layer.endEditCommand()\n",[14,1245,1246,1249,1250,1253,1254,1257,1258,1260,1261,1265],{},[34,1247,1248],{"href":248},"Adding features to a vector layer"," shows both routes side by side, including how to recover the primary key the database assigns and how ",[18,1251,1252],{},"QgsVectorLayerUtils.createFeature"," applies default values and constraints the way a form would. ",[34,1255,1256],{"href":278},"Undoing edits with the edit buffer"," goes deeper on the buffer: grouping a batch into one undo step, inspecting pending changes, reacting to edit signals, and what to do when ",[18,1259,1155],{}," returns False. Where several layers must change together, ",[34,1262,1264],{"href":1263},"\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Fedit-features-with-transactions-pyqgis\u002F","transaction groups"," put them into one database transaction.",[208,1267,1269],{"id":1268},"how-this-fits-with-the-rest-of-pyqgis","How this fits with the rest of PyQGIS",[14,1271,1272,1273,1277,1278,1282,1283,555],{},"The data model is the common ground under every other section of this site. Processing algorithms read features through the same requests and write them through the same providers, which is why ",[34,1274,1276],{"href":1275},"\u002Fspatial-data-processing-automation\u002Fbatch-processing-with-pyqgis\u002F","batch processing"," scripts can mix algorithm calls with hand-written loops. Renderers and labels read attributes by field name, so the NULL rules apply to ",[34,1279,1281],{"href":1280},"\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-expressions\u002F","expressions in styling"," as much as to Python. Plugins that add map tools or forms are, underneath, code that creates features, edits attributes and changes geometry through the edit buffer. And when a project outgrows QGIS's own tools, the same features convert to GeoDataFrames and shapely objects, as covered in ",[34,1284,1286],{"href":1285},"\u002Fspatial-data-processing-automation\u002Fpyqgis-and-the-python-data-stack\u002F","PyQGIS and the Python data stack",[208,1288,1290],{"id":1289},"making-geometry-fit-its-destination","Making geometry fit its destination",[14,1292,1293,1294,1297,1298,1301],{},"Data rarely arrives in exactly the shape a target layer expects. A dissolve returns multipolygons for a single-polygon table, a CAD export brings circular arcs into a format that cannot store them, a GPS survey carries elevation into a 2D database, and an analysis needs points where the source has buildings. Each mismatch either fails loudly — ",[18,1295,1296],{},"addFeatures"," returns False — or, worse, succeeds with silent loss: a provider dropping Z values, or ",[18,1299,1300],{},"convertToSingleType()"," keeping only the first part of a multipolygon.",[14,1303,1304],{},"The safe approach is to make conversions explicit and to do them at the boundary between steps, where you know both the incoming and the required type:",[310,1306,1308],{"className":312,"code":1307,"language":314,"meta":315,"style":315},"from qgis.core import QgsWkbTypes\n\ndef fit_to_layer(geom, layer):\n    target = layer.wkbType()\n    if QgsWkbTypes.isMultiType(target):\n        geom.convertToMultiType()\n    elif geom.isMultipart() and geom.constGet().partCount() > 1:\n        raise ValueError(\"multipart geometry for a single-part layer: split it first\")\n    if geom.constGet().hasCurvedSegments() and not QgsWkbTypes.isCurvedType(target):\n        geom = QgsGeometry(geom.constGet().segmentize())\n    if QgsWkbTypes.hasZ(target) and not geom.constGet().is3D():\n        geom.get().addZValue(0.0)\n    elif not QgsWkbTypes.hasZ(target) and geom.constGet().is3D():\n        geom.get().dropZValue()\n    return geom\n",[18,1309,1310,1321,1325,1337,1347,1355,1360,1383,1398,1414,1425,1440,1451,1464,1470],{"__ignoreMap":315},[319,1311,1312,1314,1316,1318],{"class":127,"line":321},[319,1313,325],{"class":324},[319,1315,329],{"class":328},[319,1317,332],{"class":324},[319,1319,1320],{"class":328}," QgsWkbTypes\n",[319,1322,1323],{"class":127,"line":338},[319,1324,342],{"emptyLinePlaceholder":341},[319,1326,1327,1330,1334],{"class":127,"line":345},[319,1328,1329],{"class":324},"def",[319,1331,1333],{"class":1332},"svObZ"," fit_to_layer",[319,1335,1336],{"class":328},"(geom, layer):\n",[319,1338,1339,1342,1344],{"class":127,"line":370},[319,1340,1341],{"class":328},"    target ",[319,1343,351],{"class":324},[319,1345,1346],{"class":328}," layer.wkbType()\n",[319,1348,1349,1352],{"class":127,"line":388},[319,1350,1351],{"class":324},"    if",[319,1353,1354],{"class":328}," QgsWkbTypes.isMultiType(target):\n",[319,1356,1357],{"class":127,"line":399},[319,1358,1359],{"class":328},"        geom.convertToMultiType()\n",[319,1361,1362,1365,1368,1371,1374,1377,1380],{"class":127,"line":450},[319,1363,1364],{"class":324},"    elif",[319,1366,1367],{"class":328}," geom.isMultipart() ",[319,1369,1370],{"class":324},"and",[319,1372,1373],{"class":328}," geom.constGet().partCount() ",[319,1375,1376],{"class":324},">",[319,1378,1379],{"class":364}," 1",[319,1381,1382],{"class":328},":\n",[319,1384,1385,1388,1391,1393,1396],{"class":127,"line":1095},[319,1386,1387],{"class":324},"        raise",[319,1389,1390],{"class":364}," ValueError",[319,1392,405],{"class":328},[319,1394,1395],{"class":357},"\"multipart geometry for a single-part layer: split it first\"",[319,1397,481],{"class":328},[319,1399,1401,1403,1406,1408,1411],{"class":127,"line":1400},9,[319,1402,1351],{"class":324},[319,1404,1405],{"class":328}," geom.constGet().hasCurvedSegments() ",[319,1407,1370],{"class":324},[319,1409,1410],{"class":324}," not",[319,1412,1413],{"class":328}," QgsWkbTypes.isCurvedType(target):\n",[319,1415,1417,1420,1422],{"class":127,"line":1416},10,[319,1418,1419],{"class":328},"        geom ",[319,1421,351],{"class":324},[319,1423,1424],{"class":328}," QgsGeometry(geom.constGet().segmentize())\n",[319,1426,1428,1430,1433,1435,1437],{"class":127,"line":1427},11,[319,1429,1351],{"class":324},[319,1431,1432],{"class":328}," QgsWkbTypes.hasZ(target) ",[319,1434,1370],{"class":324},[319,1436,1410],{"class":324},[319,1438,1439],{"class":328}," geom.constGet().is3D():\n",[319,1441,1443,1446,1449],{"class":127,"line":1442},12,[319,1444,1445],{"class":328},"        geom.get().addZValue(",[319,1447,1448],{"class":364},"0.0",[319,1450,481],{"class":328},[319,1452,1454,1456,1458,1460,1462],{"class":127,"line":1453},13,[319,1455,1364],{"class":324},[319,1457,1410],{"class":324},[319,1459,1432],{"class":328},[319,1461,1370],{"class":324},[319,1463,1439],{"class":328},[319,1465,1467],{"class":127,"line":1466},14,[319,1468,1469],{"class":328},"        geom.get().dropZValue()\n",[319,1471,1473,1476],{"class":127,"line":1472},15,[319,1474,1475],{"class":324},"    return",[319,1477,1478],{"class":328}," geom\n",[14,1480,1481,1483,1484,1487],{},[222,1482,486],{}," The helper refuses the one conversion that loses data — collapsing several parts into one — and performs the lossless or deliberate ones. Segmentizing uses the default tolerance, fine for most display purposes; pass an explicit tolerance when accuracy matters. Adding a constant Z of zero is a placeholder that should be documented, or replaced by draping onto a DEM. ",[34,1485,1486],{"href":288},"Converting geometry types"," covers each of these conversions in detail, including the family changes — polygon to boundary line, line to polygon, any shape to an interior point — and the Processing algorithms that apply them to whole layers.",[208,1489,1491],{"id":1490},"python-loops-or-processing","Python loops or Processing?",[14,1493,1494],{},"Everything in this guide can be done feature by feature in Python, and much of it can also be done by a Processing algorithm on a whole layer. Neither is always better.",[14,1496,1497],{},[42,1498,1501,1504,1507,1509,1512,1516,1521,1525,1529,1533,1537,1539,1542,1545,1548,1551],{"viewBox":1499,"role":45,"ariaLabel":1500,"xmlns":47},"0 0 760 250","A comparison of Python feature loops and Processing algorithms, listing when each is the better choice",[49,1502,1503],{},"When to loop and when to call an algorithm",[53,1505,1506],{},"A Python loop over features suits per-feature logic that no algorithm provides, row-level error handling, building features from non-GIS sources, and small layers. A Processing algorithm suits standard operations such as buffer, dissolve or type conversion on large layers, steps that should appear in a model or the history, and work that benefits from the algorithm's native C++ speed and its handling of invalid geometry.",[57,1508],{"x":59,"y":59,"width":60,"height":94,"fill":62},[81,1510,1511],{"x":83,"y":84,"style":85,"fill":86,"textAnchor":87},"Same result, different strengths",[57,1513],{"x":91,"y":92,"width":1514,"height":1515,"rx":71,"fill":142,"stroke":143,"style":97},"340","176",[81,1517,1520],{"x":1518,"y":1519,"style":102,"fill":143,"textAnchor":87},"194","91.78","Python loop",[81,1522,1524],{"x":1518,"y":1523,"style":107,"fill":79,"textAnchor":87},"117.78","custom per-feature logic",[81,1526,1528],{"x":1518,"y":1527,"style":107,"fill":79,"textAnchor":87},"143.78","error per source row",[81,1530,1532],{"x":1518,"y":1531,"style":107,"fill":79,"textAnchor":87},"169.78","input from APIs, CSV, JSON",[81,1534,1536],{"x":1518,"y":1535,"style":107,"fill":79,"textAnchor":87},"195.78","small to medium layers",[57,1538],{"x":939,"y":92,"width":1514,"height":1515,"rx":71,"fill":95,"stroke":96,"style":97},[81,1540,1541],{"x":180,"y":1519,"style":102,"fill":96,"textAnchor":87},"Processing algorithm",[81,1543,1544],{"x":180,"y":1523,"style":107,"fill":79,"textAnchor":87},"standard operations",[81,1546,1547],{"x":180,"y":1527,"style":107,"fill":79,"textAnchor":87},"large layers, native speed",[81,1549,1550],{"x":180,"y":1531,"style":107,"fill":79,"textAnchor":87},"invalid-geometry handling",[81,1552,1553],{"x":180,"y":1535,"style":107,"fill":79,"textAnchor":87},"reusable in models, history",[14,1555,1556,1557,1561],{},"A useful rule: if an algorithm exists for the operation, use it, and write a loop only for the part that is genuinely yours. Reading features from an unfamiliar CSV, deciding row by row which ones to keep and logging why — that is a loop. Buffering, dissolving and reprojecting the result — those are algorithm calls. Mixing the two is natural because both read and write the same layers; memory layers make the hand-off free, and ",[34,1558,1560],{"href":1559},"\u002Fspatial-data-processing-automation\u002Fchaining-processing-algorithms\u002F","chaining Processing algorithms"," shows how to pass results from one step to the next without touching disk.",[208,1563,1565],{"id":1564},"common-failure-patterns","Common failure patterns",[14,1567,1568],{},"Most data-model bugs fall into a handful of recognisable shapes. Knowing them makes the symptoms quick to diagnose.",[216,1570,1571,1584,1597,1611,1621,1627],{},[219,1572,1573,1576,1577,1580,1581,555],{},[222,1574,1575],{},"The layer is valid but empty on the map."," A memory layer was created without ",[18,1578,1579],{},"crs=",", or its extent was never updated after a provider write. Fix the URI and call ",[18,1582,1583],{},"updateExtents()",[219,1585,1586,1589,1590,1593,1594,1596],{},[222,1587,1588],{},"Every attribute is shifted by one column."," A feature was built with ",[18,1591,1592],{},"setAttributes"," and a list that skipped the primary key. Build features from ",[18,1595,304],{}," and assign by name.",[219,1598,1599,1606,1607,1610],{},[222,1600,1601,1602,1605],{},"Comparisons raise ",[18,1603,1604],{},"TypeError"," on some rows only."," Those rows hold NULL. Filter with a NULL-aware helper or ",[18,1608,1609],{},"IS NOT NULL"," in the request.",[219,1612,1613,1616,1617,1620],{},[222,1614,1615],{},"An append returns False with no message."," Geometry type mismatch, most often single into multi or 3D into 2D. Convert first, then read ",[18,1618,1619],{},"provider.lastError()"," for anything else.",[219,1622,1623,1626],{},[222,1624,1625],{},"Edits disappear after a restart."," The layer was left in edit mode, or it was a memory layer that was never saved.",[219,1628,1629,1634],{},[222,1630,1631,555],{},[18,1632,1633],{},"RuntimeError: wrapped C\u002FC++ object has been deleted"," The layer was removed from the project while Python still held a reference. Stop using it after removal, or keep a clone.",[14,1636,1637],{},"Each of these is covered in the recipe for the step where it occurs, with the check that catches it before it spreads.",[208,1639,1641],{"id":1640},"key-takeaways","Key takeaways",[216,1643,1644,1647,1650,1653,1659,1668,1671],{},[219,1645,1646],{},"A feature is an id, attribute values in the layer's field order, and one geometry; create features from the layer's fields so positions always line up.",[219,1648,1649],{},"Geometry has a type and coordinates but no CRS; the layer supplies the CRS, and transforms must be explicit.",[219,1651,1652],{},"Memory layers are full layers held in RAM — declare them completely in the URI and save anything worth keeping.",[219,1654,1655,1656,1658],{},"Narrow every read with a ",[18,1657,885],{},": rows, columns, geometry, order and limit are pushed down to the provider.",[219,1660,1661,1662,1664,1665,1667],{},"Empty values are ",[18,1663,976],{}," on QGIS 3 and ",[18,1666,980],{}," on QGIS 4; test for them with a helper that accepts both.",[219,1669,1670],{},"Write through the provider for unattended scripts and through the edit buffer when people share the layer; group script edits into one undo step.",[219,1672,1673],{},"Match geometry types before writing: promote to multi, take boundaries, segmentize curves and drop or add Z and M as the target requires.",[208,1675,1677],{"id":1676},"frequently-asked-questions","Frequently Asked Questions",[14,1679,1680,1689,1691,1692,1695,1696,1698,1699,1701],{},[222,1681,1682,1683,996,1685,1688],{},"What is the difference between ",[18,1684,658],{},[18,1686,1687],{},"QgsPointXY","?",[18,1690,1687],{}," is a lightweight 2D coordinate pair used by the ",[18,1693,1694],{},"…XY"," convenience methods. ",[18,1697,658],{}," is a full geometry type that can carry Z and M values. Use ",[18,1700,658],{}," whenever elevation or measures matter.",[14,1703,1704,1707],{},[222,1705,1706],{},"Should I use memory layers or temporary GeoPackages for intermediate data?","\nMemory layers for anything that fits comfortably in RAM and lives only for the script's run; a temporary GeoPackage for very large intermediates, or when another process needs to read them.",[14,1709,1710,1717,1718,1720],{},[222,1711,1712,1713,1716],{},"Why does ",[18,1714,1715],{},"layer.getFeatures()"," sometimes return features in a different order?","\nWithout ",[18,1719,959],{},", order is whatever the provider returns, which for databases is not guaranteed. Always order explicitly when it matters.",[14,1722,1723,1726],{},[222,1724,1725],{},"Can I change a layer's geometry type after creation?","\nNo. Create a new layer of the required type and copy features into it, converting geometries on the way.",[14,1728,1729,1732],{},[222,1730,1731],{},"Are feature ids stable?","\nOn GeoPackage and PostGIS they are the primary key and stable. On shapefiles they are row numbers and can change when the file is rewritten. Use a business key field for anything that must persist.",[208,1734,1736],{"id":1735},"related","Related",[216,1738,1739,1744,1751,1757,1764,1771,1776,1781,1786,1791,1796,1801],{},[219,1740,1741,1743],{},[34,1742,37],{"href":36}," — the section this guide belongs to",[219,1745,1746,1750],{},[34,1747,1749],{"href":1748},"\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-projects\u002F","Working with QGIS Projects"," — the project that layers and features live in",[219,1752,1753,1756],{},[34,1754,1755],{"href":1280},"Working with QGIS Expressions"," — filters and computed values over feature attributes",[219,1758,1759,1763],{},[34,1760,1762],{"href":1761},"\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-api-architecture\u002F","QGIS API Architecture"," — modules, object ownership and signals behind these classes",[219,1765,1766,1770],{},[34,1767,1769],{"href":1768},"\u002Fspatial-data-processing-automation\u002Fgeometry-operations-and-predicates\u002F","Geometry Operations & Predicates"," — buffers, intersections and spatial tests on the geometries described here",[219,1772,1773],{},[34,1774,1775],{"href":228},"Create a Memory Layer in PyQGIS",[219,1777,1778],{},[34,1779,1780],{"href":238},"Create Geometry from WKT and GeoJSON in PyQGIS",[219,1782,1783],{},[34,1784,1785],{"href":248},"Add Features to a Vector Layer in PyQGIS",[219,1787,1788],{},[34,1789,1790],{"href":258},"Read Feature Attributes and Geometry in PyQGIS",[219,1792,1793],{},[34,1794,1795],{"href":268},"Handle NULL Values and QVariant in PyQGIS",[219,1797,1798],{},[34,1799,1800],{"href":278},"Undo Edits with the Edit Buffer in PyQGIS",[219,1802,1803],{},[34,1804,1805],{"href":288},"Convert Geometry Types in PyQGIS",[1807,1808,1809],"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 .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}",{"title":315,"searchDepth":338,"depth":338,"links":1811},[1812,1813,1814,1815,1816,1817,1818,1819,1820,1821,1822,1823,1824],{"id":210,"depth":338,"text":211},{"id":293,"depth":338,"text":294},{"id":533,"depth":338,"text":534},{"id":783,"depth":338,"text":784},{"id":874,"depth":338,"text":875},{"id":1113,"depth":338,"text":1114},{"id":1268,"depth":338,"text":1269},{"id":1289,"depth":338,"text":1290},{"id":1490,"depth":338,"text":1491},{"id":1564,"depth":338,"text":1565},{"id":1640,"depth":338,"text":1641},{"id":1676,"depth":338,"text":1677},{"id":1735,"depth":338,"text":1736},"The data model under every PyQGIS script — QgsFeature, QgsFields and QgsGeometry — with practical guides to memory layers, building geometry from WKT and GeoJSON, reading and adding features, NULL values, the edit buffer and geometry type conversion.","md",{"slug":1828,"type":1829,"breadcrumb":1830,"datePublished":1831,"dateModified":1831},"features-geometries-and-memory-layers","guide","Features, Geometries & Memory Layers","2026-10-02","\u002Fpyqgis-fundamentals-environment-setup\u002Ffeatures-geometries-and-memory-layers",{"title":5,"description":1825},"pyqgis-fundamentals-environment-setup\u002Ffeatures-geometries-and-memory-layers\u002Findex","wUeNg4iZVzP6crEwMKgqkvN1gMAI4gxbQMYnqXZxjKg",1790966252922]