[{"data":1,"prerenderedAt":1755},["ShallowReactive",2],{"doc:\u002Fspatial-data-processing-automation\u002Fpyqgis-and-the-python-data-stack\u002Fconvert-between-qgsgeometry-and-shapely":3},{"id":4,"title":5,"body":6,"description":1745,"extension":1746,"meta":1747,"navigation":225,"path":1751,"seo":1752,"stem":1753,"__hash__":1754},"docs\u002Fspatial-data-processing-automation\u002Fpyqgis-and-the-python-data-stack\u002Fconvert-between-qgsgeometry-and-shapely\u002Findex.md","Convert Between QgsGeometry and Shapely",{"type":7,"value":8,"toc":1731},"minimark",[9,13,17,26,153,158,178,182,185,483,523,527,534,743,770,824,828,831,1033,1056,1060,1063,1132,1237,1246,1250,1253,1413,1441,1445,1448,1483,1572,1584,1588,1609,1613,1650,1654,1660,1664,1670,1676,1686,1692,1696,1727],[10,11,5],"h1",{"id":12},"convert-between-qgsgeometry-and-shapely",[14,15,16],"p",{},"QGIS and shapely both wrap the GEOS library, so they agree on what a geometry is and on most operations. They differ in everything around it: shapely 2 offers vectorised functions over NumPy arrays of geometries, a huge ecosystem builds on it, and many snippets on the web assume it; QGIS offers curved geometries, M values, coordinate transforms and its own rendering. Converting between them is a routine bridge, and getting it right — fast, lossless, and with clear handling of edge cases — makes mixed scripts simple.",[14,18,19,20,25],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fspatial-data-processing-automation\u002Fpyqgis-and-the-python-data-stack\u002F","PyQGIS and the Python Data Stack",". It converts single geometries and whole layers in both directions, shows what survives the trip and what does not, and identifies the shapely operations that are worth converting for.",[14,27,28],{},[29,30,35,39,43,50,67,76,85,91,96,100,105,110,114,117,120,123,130,135,139,143,149],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"xmlns":34},"0 0 760 270","img","Geometry passing between QgsGeometry and shapely as WKB bytes in both directions, with what each library adds on top of the shared GEOS core","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[36,37,38],"title",{},"WKB both ways",[40,41,42],"desc",{},"QgsGeometry.asWkb produces bytes that shapely.from_wkb reads; shapely's to_wkb or the wkb attribute produces bytes that QgsGeometry.fromWkb reads. Both libraries use GEOS for most operations, so results agree. QGIS adds curves, M values and CRS-aware tools; shapely adds vectorised array operations and a large ecosystem.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","270","#f6f3ea",[51,52,53],"defs",{},[54,55,62],"marker",{"id":56,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"shpBridgeArrow","0 0 10 10","8","5","7","auto-start-reverse",[63,64],"path",{"d":65,"fill":66},"M0 0 L10 5 L0 10 z","#2f3b35",[68,69,75],"text",{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"380","28","text-anchor:middle;font-size:14px;font-family:sans-serif;font-weight:bold","#17211d","middle","Two wrappers around the same engine",[44,77],{"x":78,"y":79,"width":80,"height":81,"rx":58,"fill":82,"stroke":83,"style":84},"24","52","230","150","#eef7f4","#0f766e","stroke-width:2",[68,86,90],{"x":87,"y":88,"style":89,"fill":83,"textAnchor":74},"139","88.78","text-anchor:middle;font-size:11.5px;font-family:sans-serif;font-weight:bold","QgsGeometry",[68,92,95],{"x":87,"y":93,"style":94,"fill":66,"textAnchor":74},"116.78","text-anchor:middle;font-size:10.5px;font-family:sans-serif","curves, M values",[68,97,99],{"x":87,"y":98,"style":94,"fill":66,"textAnchor":74},"144.78","transforms, rendering",[68,101,104],{"x":87,"y":102,"style":94,"fill":103,"textAnchor":74},"172.78","#59645f","QGIS API",[44,106],{"x":107,"y":79,"width":80,"height":81,"rx":58,"fill":108,"stroke":109,"style":84},"506","#eff3ff","#2563eb",[68,111,113],{"x":112,"y":88,"style":89,"fill":109,"textAnchor":74},"621","shapely",[68,115,116],{"x":112,"y":93,"style":94,"fill":66,"textAnchor":74},"vectorised arrays",[68,118,119],{"x":112,"y":98,"style":94,"fill":66,"textAnchor":74},"ecosystem",[68,121,122],{"x":112,"y":102,"style":94,"fill":103,"textAnchor":74},"numpy, GeoPandas",[124,125],"line",{"x1":126,"y1":127,"x2":128,"y2":127,"stroke":66,"style":129},"254","100","502","stroke-width:1.8;marker-end:url(#shpBridgeArrow)",[68,131,134],{"x":70,"y":132,"style":133,"fill":66,"textAnchor":74},"92","text-anchor:middle;font-size:10.5px;font-family:monospace","asWkb() → from_wkb()",[124,136],{"x1":107,"y1":137,"x2":138,"y2":137,"stroke":66,"style":129},"160","258",[68,140,142],{"x":70,"y":141,"style":133,"fill":66,"textAnchor":74},"152","to_wkb() → fromWkb()",[44,144],{"x":80,"y":145,"width":146,"height":147,"rx":58,"fill":148,"stroke":103,"style":84},"222","300","36","#fffdf7",[68,150,152],{"x":70,"y":151,"style":89,"fill":73,"textAnchor":74},"243.78","GEOS underneath both",[154,155,157],"h2",{"id":156},"prerequisites","Prerequisites",[159,160,161,165],"ul",{},[162,163,164],"li",{},"QGIS 3.34 LTR or newer, or the QGIS 4 series.",[162,166,167,168,172,173,177],{},"Shapely 2.0 or newer in QGIS's Python. Some QGIS installers ship it; otherwise see ",[21,169,171],{"href":170},"\u002Fpyqgis-fundamentals-environment-setup\u002Fvirtual-environments-for-gis\u002Finstall-python-packages-into-qgis\u002F","installing Python packages into QGIS",". Check with ",[174,175,176],"code",{},"import shapely; shapely.__version__",".",[154,179,181],{"id":180},"convert-a-single-geometry","Convert a single geometry",[14,183,184],{},"WKB is the lossless bridge: binary, compact, and read natively by both libraries. A pair of small functions covers everyday use.",[186,187,192],"pre",{"className":188,"code":189,"language":190,"meta":191,"style":191},"language-python shiki shiki-themes github-dark","import shapely\nfrom qgis.core import QgsGeometry\n\ndef to_shapely(qgeom):\n    if qgeom is None or qgeom.isNull():\n        return None\n    return shapely.from_wkb(bytes(qgeom.asWkb()))\n\ndef to_qgis(sgeom):\n    g = QgsGeometry()\n    if sgeom is None:\n        return g\n    g.fromWkb(shapely.to_wkb(sgeom, include_srid=False))\n    return g\n\nq = QgsGeometry.fromWkt(\"Polygon ((0 0, 10 0, 10 10, 0 10, 0 0))\")\ns = to_shapely(q)\nprint(type(s).__name__, s.area)                # Polygon 100.0\nback = to_qgis(s.buffer(1, quad_segs=4))\nprint(back.asWkt(1)[:50], \"…\", round(back.area(), 2))\n","python","",[174,193,194,206,220,227,240,262,271,286,291,302,314,329,337,355,362,367,385,396,421,448],{"__ignoreMap":191},[195,196,198,202],"span",{"class":124,"line":197},1,[195,199,201],{"class":200},"snl16","import",[195,203,205],{"class":204},"s95oV"," shapely\n",[195,207,209,212,215,217],{"class":124,"line":208},2,[195,210,211],{"class":200},"from",[195,213,214],{"class":204}," qgis.core ",[195,216,201],{"class":200},[195,218,219],{"class":204}," QgsGeometry\n",[195,221,223],{"class":124,"line":222},3,[195,224,226],{"emptyLinePlaceholder":225},true,"\n",[195,228,230,233,237],{"class":124,"line":229},4,[195,231,232],{"class":200},"def",[195,234,236],{"class":235},"svObZ"," to_shapely",[195,238,239],{"class":204},"(qgeom):\n",[195,241,243,246,249,252,256,259],{"class":124,"line":242},5,[195,244,245],{"class":200},"    if",[195,247,248],{"class":204}," qgeom ",[195,250,251],{"class":200},"is",[195,253,255],{"class":254},"sDLfK"," None",[195,257,258],{"class":200}," or",[195,260,261],{"class":204}," qgeom.isNull():\n",[195,263,265,268],{"class":124,"line":264},6,[195,266,267],{"class":200},"        return",[195,269,270],{"class":254}," None\n",[195,272,274,277,280,283],{"class":124,"line":273},7,[195,275,276],{"class":200},"    return",[195,278,279],{"class":204}," shapely.from_wkb(",[195,281,282],{"class":254},"bytes",[195,284,285],{"class":204},"(qgeom.asWkb()))\n",[195,287,289],{"class":124,"line":288},8,[195,290,226],{"emptyLinePlaceholder":225},[195,292,294,296,299],{"class":124,"line":293},9,[195,295,232],{"class":200},[195,297,298],{"class":235}," to_qgis",[195,300,301],{"class":204},"(sgeom):\n",[195,303,305,308,311],{"class":124,"line":304},10,[195,306,307],{"class":204},"    g ",[195,309,310],{"class":200},"=",[195,312,313],{"class":204}," QgsGeometry()\n",[195,315,317,319,322,324,326],{"class":124,"line":316},11,[195,318,245],{"class":200},[195,320,321],{"class":204}," sgeom ",[195,323,251],{"class":200},[195,325,255],{"class":254},[195,327,328],{"class":204},":\n",[195,330,332,334],{"class":124,"line":331},12,[195,333,267],{"class":200},[195,335,336],{"class":204}," g\n",[195,338,340,343,347,349,352],{"class":124,"line":339},13,[195,341,342],{"class":204},"    g.fromWkb(shapely.to_wkb(sgeom, ",[195,344,346],{"class":345},"s9osk","include_srid",[195,348,310],{"class":200},[195,350,351],{"class":254},"False",[195,353,354],{"class":204},"))\n",[195,356,358,360],{"class":124,"line":357},14,[195,359,276],{"class":200},[195,361,336],{"class":204},[195,363,365],{"class":124,"line":364},15,[195,366,226],{"emptyLinePlaceholder":225},[195,368,370,373,375,378,382],{"class":124,"line":369},16,[195,371,372],{"class":204},"q ",[195,374,310],{"class":200},[195,376,377],{"class":204}," QgsGeometry.fromWkt(",[195,379,381],{"class":380},"sU2Wk","\"Polygon ((0 0, 10 0, 10 10, 0 10, 0 0))\"",[195,383,384],{"class":204},")\n",[195,386,388,391,393],{"class":124,"line":387},17,[195,389,390],{"class":204},"s ",[195,392,310],{"class":200},[195,394,395],{"class":204}," to_shapely(q)\n",[195,397,399,402,405,408,411,414,417],{"class":124,"line":398},18,[195,400,401],{"class":254},"print",[195,403,404],{"class":204},"(",[195,406,407],{"class":254},"type",[195,409,410],{"class":204},"(s).",[195,412,413],{"class":254},"__name__",[195,415,416],{"class":204},", s.area)                ",[195,418,420],{"class":419},"sjoCn","# Polygon 100.0\n",[195,422,424,427,429,432,435,438,441,443,446],{"class":124,"line":423},19,[195,425,426],{"class":204},"back ",[195,428,310],{"class":200},[195,430,431],{"class":204}," to_qgis(s.buffer(",[195,433,434],{"class":254},"1",[195,436,437],{"class":204},", ",[195,439,440],{"class":345},"quad_segs",[195,442,310],{"class":200},[195,444,445],{"class":254},"4",[195,447,354],{"class":204},[195,449,451,453,456,458,461,464,467,470,472,475,478,481],{"class":124,"line":450},20,[195,452,401],{"class":254},[195,454,455],{"class":204},"(back.asWkt(",[195,457,434],{"class":254},[195,459,460],{"class":204},")[:",[195,462,463],{"class":254},"50",[195,465,466],{"class":204},"], ",[195,468,469],{"class":380},"\"…\"",[195,471,437],{"class":204},[195,473,474],{"class":254},"round",[195,476,477],{"class":204},"(back.area(), ",[195,479,480],{"class":254},"2",[195,482,354],{"class":204},[14,484,485,489,490,493,494,497,498,501,502,505,506,509,510,512,513,516,517,519,520,522],{},[486,487,488],"strong",{},"Breakdown:"," ",[174,491,492],{},"asWkb()"," returns a ",[174,495,496],{},"QByteArray","; wrapping it in ",[174,499,500],{},"bytes()"," gives shapely the plain bytes it expects. In the other direction, ",[174,503,504],{},"to_wkb"," without an SRID produces standard ISO WKB that ",[174,507,508],{},"fromWkb"," reads into an existing ",[174,511,90],{},". A null QGIS geometry maps to ",[174,514,515],{},"None",", and ",[174,518,515],{}," maps to an empty ",[174,521,90],{},", so missing geometries pass through cleanly in both directions. The buffer round trip shows an operation done in shapely and its result returned to QGIS without any loss of precision.",[154,524,526],{"id":525},"convert-whole-layers-at-once","Convert whole layers at once",[14,528,529,530,533],{},"Shapely 2 functions accept arrays. Collecting WKB from all features and converting in one call is dramatically faster than calling ",[174,531,532],{},"to_shapely"," per feature, and the result is a NumPy array ready for vectorised operations.",[186,535,537],{"className":188,"code":536,"language":190,"meta":191,"style":191},"import numpy as np\nfrom qgis.core import QgsProject, QgsFeatureRequest\n\nparcels = QgsProject.instance().mapLayersByName(\"parcels\")[0]\nreq = QgsFeatureRequest().setNoAttributes()\nfids, wkbs = [], []\nfor f in parcels.getFeatures(req):\n    fids.append(f.id())\n    wkbs.append(bytes(f.geometry().asWkb()) if f.hasGeometry() else None)\n\ngeoms = shapely.from_wkb(wkbs)                 # numpy array of shapely geometries\nareas = shapely.area(geoms)\ncompact = 4 * np.pi * areas \u002F shapely.length(geoms) ** 2\nprint(len(geoms), \"parcels; median compactness\", round(float(np.nanmedian(compact)), 3))\n",[174,538,539,552,563,567,588,598,608,622,627,650,654,667,677,711],{"__ignoreMap":191},[195,540,541,543,546,549],{"class":124,"line":197},[195,542,201],{"class":200},[195,544,545],{"class":204}," numpy ",[195,547,548],{"class":200},"as",[195,550,551],{"class":204}," np\n",[195,553,554,556,558,560],{"class":124,"line":208},[195,555,211],{"class":200},[195,557,214],{"class":204},[195,559,201],{"class":200},[195,561,562],{"class":204}," QgsProject, QgsFeatureRequest\n",[195,564,565],{"class":124,"line":222},[195,566,226],{"emptyLinePlaceholder":225},[195,568,569,572,574,577,580,583,585],{"class":124,"line":229},[195,570,571],{"class":204},"parcels ",[195,573,310],{"class":200},[195,575,576],{"class":204}," QgsProject.instance().mapLayersByName(",[195,578,579],{"class":380},"\"parcels\"",[195,581,582],{"class":204},")[",[195,584,46],{"class":254},[195,586,587],{"class":204},"]\n",[195,589,590,593,595],{"class":124,"line":242},[195,591,592],{"class":204},"req ",[195,594,310],{"class":200},[195,596,597],{"class":204}," QgsFeatureRequest().setNoAttributes()\n",[195,599,600,603,605],{"class":124,"line":264},[195,601,602],{"class":204},"fids, wkbs ",[195,604,310],{"class":200},[195,606,607],{"class":204}," [], []\n",[195,609,610,613,616,619],{"class":124,"line":273},[195,611,612],{"class":200},"for",[195,614,615],{"class":204}," f ",[195,617,618],{"class":200},"in",[195,620,621],{"class":204}," parcels.getFeatures(req):\n",[195,623,624],{"class":124,"line":288},[195,625,626],{"class":204},"    fids.append(f.id())\n",[195,628,629,632,634,637,640,643,646,648],{"class":124,"line":293},[195,630,631],{"class":204},"    wkbs.append(",[195,633,282],{"class":254},[195,635,636],{"class":204},"(f.geometry().asWkb()) ",[195,638,639],{"class":200},"if",[195,641,642],{"class":204}," f.hasGeometry() ",[195,644,645],{"class":200},"else",[195,647,255],{"class":254},[195,649,384],{"class":204},[195,651,652],{"class":124,"line":304},[195,653,226],{"emptyLinePlaceholder":225},[195,655,656,659,661,664],{"class":124,"line":316},[195,657,658],{"class":204},"geoms ",[195,660,310],{"class":200},[195,662,663],{"class":204}," shapely.from_wkb(wkbs)                 ",[195,665,666],{"class":419},"# numpy array of shapely geometries\n",[195,668,669,672,674],{"class":124,"line":331},[195,670,671],{"class":204},"areas ",[195,673,310],{"class":200},[195,675,676],{"class":204}," shapely.area(geoms)\n",[195,678,679,682,684,687,690,693,696,699,702,705,708],{"class":124,"line":339},[195,680,681],{"class":204},"compact ",[195,683,310],{"class":200},[195,685,686],{"class":254}," 4",[195,688,689],{"class":200}," *",[195,691,692],{"class":204}," np.pi ",[195,694,695],{"class":200},"*",[195,697,698],{"class":204}," areas ",[195,700,701],{"class":200},"\u002F",[195,703,704],{"class":204}," shapely.length(geoms) ",[195,706,707],{"class":200},"**",[195,709,710],{"class":254}," 2\n",[195,712,713,715,717,720,723,726,728,730,732,735,738,741],{"class":124,"line":357},[195,714,401],{"class":254},[195,716,404],{"class":204},[195,718,719],{"class":254},"len",[195,721,722],{"class":204},"(geoms), ",[195,724,725],{"class":380},"\"parcels; median compactness\"",[195,727,437],{"class":204},[195,729,474],{"class":254},[195,731,404],{"class":204},[195,733,734],{"class":254},"float",[195,736,737],{"class":204},"(np.nanmedian(compact)), ",[195,739,740],{"class":254},"3",[195,742,354],{"class":204},[14,744,745,489,747,750,751,754,755,757,758,761,762,765,766,769],{},[486,746,488],{},[174,748,749],{},"setNoAttributes()"," skips reading attribute values, since only geometry is needed. ",[174,752,753],{},"shapely.from_wkb"," on a list returns an object array; ",[174,756,515],{}," entries become missing values that every shapely function skips. Vectorised functions such as ",[174,759,760],{},"area"," and ",[174,763,764],{},"length"," run in compiled code across the whole array — computing a compactness index for a hundred thousand parcels takes a fraction of a second. Keeping the ",[174,767,768],{},"fids"," list in the same order lets you write results back to the layer by feature id.",[14,771,772],{},[29,773,776,779,782,785,788,795,803,807,812,820],{"viewBox":774,"role":32,"ariaLabel":775,"xmlns":34},"0 0 760 216","Relative time for per-feature shapely conversion compared with one vectorised conversion and operation over 100,000 geometries",[36,777,778],{},"Per-feature versus vectorised",[40,780,781],{},"Relative time to compute a measure over one hundred thousand geometries. A Python loop creating one shapely object per feature and calling a method on it is slowest. Collecting WKB and converting once, then calling a vectorised function, is several times faster. The vectorised route also returns a NumPy array that can be written back to the layer in one batch.",[44,783],{"x":46,"y":46,"width":47,"height":784,"fill":49},"216",[68,786,787],{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"100,000 geometries",[68,789,794],{"x":790,"y":791,"style":792,"fill":66,"textAnchor":793},"200","84","text-anchor:end;font-size:11.0px;font-family:sans-serif","end","per-feature loop",[44,796],{"x":797,"y":798,"width":799,"height":800,"rx":740,"fill":801,"stroke":801,"style":802},"210","70","480","22","#b91c1c","stroke-width:1.5",[68,804,806],{"x":790,"y":805,"style":792,"fill":66,"textAnchor":793},"134","collect WKB, one call",[44,808],{"x":797,"y":809,"width":810,"height":800,"rx":740,"fill":811,"stroke":811,"style":802},"120","96","#15803d",[68,813,819],{"x":814,"y":815,"style":816,"fill":817,"textAnchor":818},"316","135","text-anchor:start;font-size:10.5px;font-family:sans-serif","#166534","start","≈ 5× faster",[68,821,823],{"x":70,"y":822,"style":94,"fill":103,"textAnchor":74},"196","illustrative — the gap grows with layer size",[154,825,827],{"id":826},"write-results-back-to-the-layer","Write results back to the layer",[14,829,830],{},"Results from vectorised work come back as arrays aligned with the feature ids. One provider call writes them all.",[186,832,834],{"className":188,"code":833,"language":190,"meta":191,"style":191},"from qgis.core import QgsField\nfrom qgis.PyQt.QtCore import QVariant\n\nprov = parcels.dataProvider()\nif parcels.fields().indexOf(\"compactness\") \u003C 0:\n    prov.addAttributes([QgsField(\"compactness\", QVariant.Double)])\n    parcels.updateFields()\nidx = parcels.fields().indexOf(\"compactness\")\nprov.changeAttributeValues({fid: {idx: None if np.isnan(v) else float(v)}\n                            for fid, v in zip(fids, compact)})\n\nsimplified = shapely.simplify(geoms, tolerance=0.5, preserve_topology=True)\nprov.changeGeometryValues({fid: to_qgis(g) for fid, g in zip(fids, simplified)\n                           if g is not None})\nparcels.triggerRepaint()\n",[174,835,836,847,859,863,873,894,904,909,922,943,959,963,993,1010,1028],{"__ignoreMap":191},[195,837,838,840,842,844],{"class":124,"line":197},[195,839,211],{"class":200},[195,841,214],{"class":204},[195,843,201],{"class":200},[195,845,846],{"class":204}," QgsField\n",[195,848,849,851,854,856],{"class":124,"line":208},[195,850,211],{"class":200},[195,852,853],{"class":204}," qgis.PyQt.QtCore ",[195,855,201],{"class":200},[195,857,858],{"class":204}," QVariant\n",[195,860,861],{"class":124,"line":222},[195,862,226],{"emptyLinePlaceholder":225},[195,864,865,868,870],{"class":124,"line":229},[195,866,867],{"class":204},"prov ",[195,869,310],{"class":200},[195,871,872],{"class":204}," parcels.dataProvider()\n",[195,874,875,877,880,883,886,889,892],{"class":124,"line":242},[195,876,639],{"class":200},[195,878,879],{"class":204}," parcels.fields().indexOf(",[195,881,882],{"class":380},"\"compactness\"",[195,884,885],{"class":204},") ",[195,887,888],{"class":200},"\u003C",[195,890,891],{"class":254}," 0",[195,893,328],{"class":204},[195,895,896,899,901],{"class":124,"line":264},[195,897,898],{"class":204},"    prov.addAttributes([QgsField(",[195,900,882],{"class":380},[195,902,903],{"class":204},", QVariant.Double)])\n",[195,905,906],{"class":124,"line":273},[195,907,908],{"class":204},"    parcels.updateFields()\n",[195,910,911,914,916,918,920],{"class":124,"line":288},[195,912,913],{"class":204},"idx ",[195,915,310],{"class":200},[195,917,879],{"class":204},[195,919,882],{"class":380},[195,921,384],{"class":204},[195,923,924,927,929,932,935,937,940],{"class":124,"line":293},[195,925,926],{"class":204},"prov.changeAttributeValues({fid: {idx: ",[195,928,515],{"class":254},[195,930,931],{"class":200}," if",[195,933,934],{"class":204}," np.isnan(v) ",[195,936,645],{"class":200},[195,938,939],{"class":254}," float",[195,941,942],{"class":204},"(v)}\n",[195,944,945,948,951,953,956],{"class":124,"line":304},[195,946,947],{"class":200},"                            for",[195,949,950],{"class":204}," fid, v ",[195,952,618],{"class":200},[195,954,955],{"class":254}," zip",[195,957,958],{"class":204},"(fids, compact)})\n",[195,960,961],{"class":124,"line":316},[195,962,226],{"emptyLinePlaceholder":225},[195,964,965,968,970,973,976,978,981,983,986,988,991],{"class":124,"line":331},[195,966,967],{"class":204},"simplified ",[195,969,310],{"class":200},[195,971,972],{"class":204}," shapely.simplify(geoms, ",[195,974,975],{"class":345},"tolerance",[195,977,310],{"class":200},[195,979,980],{"class":254},"0.5",[195,982,437],{"class":204},[195,984,985],{"class":345},"preserve_topology",[195,987,310],{"class":200},[195,989,990],{"class":254},"True",[195,992,384],{"class":204},[195,994,995,998,1000,1003,1005,1007],{"class":124,"line":339},[195,996,997],{"class":204},"prov.changeGeometryValues({fid: to_qgis(g) ",[195,999,612],{"class":200},[195,1001,1002],{"class":204}," fid, g ",[195,1004,618],{"class":200},[195,1006,955],{"class":254},[195,1008,1009],{"class":204},"(fids, simplified)\n",[195,1011,1012,1015,1018,1020,1023,1025],{"class":124,"line":357},[195,1013,1014],{"class":200},"                           if",[195,1016,1017],{"class":204}," g ",[195,1019,251],{"class":200},[195,1021,1022],{"class":200}," not",[195,1024,255],{"class":254},[195,1026,1027],{"class":204},"})\n",[195,1029,1030],{"class":124,"line":364},[195,1031,1032],{"class":204},"parcels.triggerRepaint()\n",[14,1034,1035,1037,1038,1041,1042,1044,1045,1048,1049,1051,1052,177],{},[486,1036,488],{}," Converting NumPy floats with ",[174,1039,1040],{},"float()"," and NaN to ",[174,1043,515],{}," gives the provider plain Python values it stores correctly. ",[174,1046,1047],{},"changeGeometryValues"," takes a dictionary of feature id to ",[174,1050,90],{},", so geometry edits made in shapely go back in one call too. Writing through the provider skips the undo stack; use an edit session instead when a person should be able to undo the change, as in ",[21,1053,1055],{"href":1054},"\u002Fpyqgis-fundamentals-environment-setup\u002Ffeatures-geometries-and-memory-layers\u002Fundo-edits-with-edit-buffer-pyqgis\u002F","undoing edits with the edit buffer",[154,1057,1059],{"id":1058},"what-survives-the-trip","What survives the trip",[14,1061,1062],{},"WKB carries everything in the OGC simple features model, but each side has things the other lacks. Knowing them prevents surprises.",[14,1064,1065],{},[29,1066,1069,1072,1075,1078,1081,1086,1090,1094,1098,1102,1107,1110,1113,1116,1119,1122,1126,1129],{"viewBox":1067,"role":32,"ariaLabel":1068,"xmlns":34},"0 0 760 242","What survives conversion: simple features and Z values cross both ways, curves and M values do not reach shapely intact, and the CRS never travels inside WKB",[36,1070,1071],{},"What crosses the bridge",[40,1073,1074],{},"Preserved both ways: points, lines, polygons, multi types, geometry collections and Z values. QGIS to shapely loses curves, which must be segmentized first, and M values, which shapely does not support before version 2.1 in all operations. Shapely to QGIS preserves everything shapely can represent. Neither side carries the CRS in plain WKB; track it separately.",[44,1076],{"x":46,"y":46,"width":47,"height":1077,"fill":49},"242",[68,1079,1080],{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"Simple features cross; curves and CRS do not",[44,1082],{"x":78,"y":1083,"width":145,"height":1084,"rx":58,"fill":1085,"stroke":811,"style":84},"56","170","#e8efe6",[68,1087,1089],{"x":815,"y":1088,"style":89,"fill":817,"textAnchor":74},"102.78","preserved",[68,1091,1093],{"x":815,"y":1092,"style":94,"fill":66,"textAnchor":74},"130.78","points, lines, polygons",[68,1095,1097],{"x":815,"y":1096,"style":94,"fill":66,"textAnchor":74},"158.78","multi + collections",[68,1099,1101],{"x":815,"y":1100,"style":94,"fill":66,"textAnchor":74},"186.78","Z values",[44,1103],{"x":1104,"y":1083,"width":145,"height":1084,"rx":58,"fill":1105,"stroke":1106,"style":84},"269","#fdf2e2","#b45309",[68,1108,1109],{"x":70,"y":1088,"style":89,"fill":1106,"textAnchor":74},"lost to shapely",[68,1111,1112],{"x":70,"y":1092,"style":94,"fill":66,"textAnchor":74},"curves → segmentize",[68,1114,1115],{"x":70,"y":1096,"style":94,"fill":66,"textAnchor":74},"M values (mostly)",[68,1117,1118],{"x":70,"y":1100,"style":94,"fill":103,"textAnchor":74},"QGIS-only features",[44,1120],{"x":1121,"y":1083,"width":145,"height":1084,"rx":58,"fill":1105,"stroke":801,"style":84},"514",[68,1123,1125],{"x":1124,"y":93,"style":89,"fill":801,"textAnchor":74},"625","never in WKB",[68,1127,1128],{"x":1124,"y":98,"style":94,"fill":66,"textAnchor":74},"the CRS",[68,1130,1131],{"x":1124,"y":102,"style":94,"fill":103,"textAnchor":74},"track it alongside",[186,1133,1135],{"className":188,"code":1134,"language":190,"meta":191,"style":191},"from qgis.core import QgsAbstractGeometry\n\narc = QgsGeometry.fromWkt(\"CircularString (0 0, 5 5, 10 0)\")\nif arc.constGet().hasCurvedSegments():\n    arc = QgsGeometry(arc.constGet().segmentize(0.01, QgsAbstractGeometry.MaximumDifference))\nprint(to_shapely(arc).geom_type, len(to_shapely(arc).coords), \"vertices\")\n\nz = QgsGeometry.fromWkt(\"LineString Z (0 0 100, 10 0 105)\")\nprint(to_shapely(z).has_z, list(to_shapely(z).coords))\n",[174,1136,1137,1148,1152,1166,1173,1189,1206,1210,1224],{"__ignoreMap":191},[195,1138,1139,1141,1143,1145],{"class":124,"line":197},[195,1140,211],{"class":200},[195,1142,214],{"class":204},[195,1144,201],{"class":200},[195,1146,1147],{"class":204}," QgsAbstractGeometry\n",[195,1149,1150],{"class":124,"line":208},[195,1151,226],{"emptyLinePlaceholder":225},[195,1153,1154,1157,1159,1161,1164],{"class":124,"line":222},[195,1155,1156],{"class":204},"arc ",[195,1158,310],{"class":200},[195,1160,377],{"class":204},[195,1162,1163],{"class":380},"\"CircularString (0 0, 5 5, 10 0)\"",[195,1165,384],{"class":204},[195,1167,1168,1170],{"class":124,"line":229},[195,1169,639],{"class":200},[195,1171,1172],{"class":204}," arc.constGet().hasCurvedSegments():\n",[195,1174,1175,1178,1180,1183,1186],{"class":124,"line":242},[195,1176,1177],{"class":204},"    arc ",[195,1179,310],{"class":200},[195,1181,1182],{"class":204}," QgsGeometry(arc.constGet().segmentize(",[195,1184,1185],{"class":254},"0.01",[195,1187,1188],{"class":204},", QgsAbstractGeometry.MaximumDifference))\n",[195,1190,1191,1193,1196,1198,1201,1204],{"class":124,"line":264},[195,1192,401],{"class":254},[195,1194,1195],{"class":204},"(to_shapely(arc).geom_type, ",[195,1197,719],{"class":254},[195,1199,1200],{"class":204},"(to_shapely(arc).coords), ",[195,1202,1203],{"class":380},"\"vertices\"",[195,1205,384],{"class":204},[195,1207,1208],{"class":124,"line":273},[195,1209,226],{"emptyLinePlaceholder":225},[195,1211,1212,1215,1217,1219,1222],{"class":124,"line":288},[195,1213,1214],{"class":204},"z ",[195,1216,310],{"class":200},[195,1218,377],{"class":204},[195,1220,1221],{"class":380},"\"LineString Z (0 0 100, 10 0 105)\"",[195,1223,384],{"class":204},[195,1225,1226,1228,1231,1234],{"class":124,"line":293},[195,1227,401],{"class":254},[195,1229,1230],{"class":204},"(to_shapely(z).has_z, ",[195,1232,1233],{"class":254},"list",[195,1235,1236],{"class":204},"(to_shapely(z).coords))\n",[14,1238,1239,1241,1242,1245],{},[486,1240,488],{}," Shapely cannot represent circular arcs; segmentizing in QGIS first, with a tolerance in map units (here one centimetre), turns them into ordinary lines. Z values cross intact and most shapely operations carry them along, though some — notably overlays — interpolate or drop them. The CRS is never in plain WKB on either side, so keep it with the data: alongside an array, in the GeoDataFrame's ",[174,1243,1244],{},"crs",", or as the layer's CRS when writing back.",[154,1247,1249],{"id":1248},"validate-and-repair-in-bulk","Validate and repair in bulk",[14,1251,1252],{},"Validity checks are a good example of work that vectorises well. Shapely can test, explain and repair every geometry of a layer in three array calls, and the results map straight back to feature ids for writing or reporting.",[186,1254,1256],{"className":188,"code":1255,"language":190,"meta":191,"style":191},"valid = shapely.is_valid(geoms)\nreasons = shapely.is_valid_reason(geoms[~valid])\nprint(int((~valid).sum()), \"invalid geometries\")\nfor fid, why in list(zip(np.array(fids)[~valid], reasons))[:5]:\n    print(fid, why)\n\nrepaired = shapely.make_valid(geoms[~valid])\narea_change = np.abs(shapely.area(repaired) - shapely.area(geoms[~valid]))\nprint(\"largest area change after repair:\", float(area_change.max()) if len(area_change) else 0)\n",[174,1257,1258,1268,1284,1306,1336,1344,1348,1362,1383],{"__ignoreMap":191},[195,1259,1260,1263,1265],{"class":124,"line":197},[195,1261,1262],{"class":204},"valid ",[195,1264,310],{"class":200},[195,1266,1267],{"class":204}," shapely.is_valid(geoms)\n",[195,1269,1270,1273,1275,1278,1281],{"class":124,"line":208},[195,1271,1272],{"class":204},"reasons ",[195,1274,310],{"class":200},[195,1276,1277],{"class":204}," shapely.is_valid_reason(geoms[",[195,1279,1280],{"class":200},"~",[195,1282,1283],{"class":204},"valid])\n",[195,1285,1286,1288,1290,1293,1296,1298,1301,1304],{"class":124,"line":222},[195,1287,401],{"class":254},[195,1289,404],{"class":204},[195,1291,1292],{"class":254},"int",[195,1294,1295],{"class":204},"((",[195,1297,1280],{"class":200},[195,1299,1300],{"class":204},"valid).sum()), ",[195,1302,1303],{"class":380},"\"invalid geometries\"",[195,1305,384],{"class":204},[195,1307,1308,1310,1313,1315,1318,1320,1323,1326,1328,1331,1333],{"class":124,"line":229},[195,1309,612],{"class":200},[195,1311,1312],{"class":204}," fid, why ",[195,1314,618],{"class":200},[195,1316,1317],{"class":254}," list",[195,1319,404],{"class":204},[195,1321,1322],{"class":254},"zip",[195,1324,1325],{"class":204},"(np.array(fids)[",[195,1327,1280],{"class":200},[195,1329,1330],{"class":204},"valid], reasons))[:",[195,1332,59],{"class":254},[195,1334,1335],{"class":204},"]:\n",[195,1337,1338,1341],{"class":124,"line":242},[195,1339,1340],{"class":254},"    print",[195,1342,1343],{"class":204},"(fid, why)\n",[195,1345,1346],{"class":124,"line":264},[195,1347,226],{"emptyLinePlaceholder":225},[195,1349,1350,1353,1355,1358,1360],{"class":124,"line":273},[195,1351,1352],{"class":204},"repaired ",[195,1354,310],{"class":200},[195,1356,1357],{"class":204}," shapely.make_valid(geoms[",[195,1359,1280],{"class":200},[195,1361,1283],{"class":204},[195,1363,1364,1367,1369,1372,1375,1378,1380],{"class":124,"line":288},[195,1365,1366],{"class":204},"area_change ",[195,1368,310],{"class":200},[195,1370,1371],{"class":204}," np.abs(shapely.area(repaired) ",[195,1373,1374],{"class":200},"-",[195,1376,1377],{"class":204}," shapely.area(geoms[",[195,1379,1280],{"class":200},[195,1381,1382],{"class":204},"valid]))\n",[195,1384,1385,1387,1389,1392,1394,1396,1399,1401,1404,1407,1409,1411],{"class":124,"line":293},[195,1386,401],{"class":254},[195,1388,404],{"class":204},[195,1390,1391],{"class":380},"\"largest area change after repair:\"",[195,1393,437],{"class":204},[195,1395,734],{"class":254},[195,1397,1398],{"class":204},"(area_change.max()) ",[195,1400,639],{"class":200},[195,1402,1403],{"class":254}," len",[195,1405,1406],{"class":204},"(area_change) ",[195,1408,645],{"class":200},[195,1410,891],{"class":254},[195,1412,384],{"class":204},[14,1414,1415,489,1417,1420,1421,1424,1425,1429,1430,1433,1434,1436,1437,1440],{},[486,1416,488],{},[174,1418,1419],{},"is_valid"," returns a boolean array; indexing with its negation selects only the invalid geometries for the slower explanation and repair calls. ",[174,1422,1423],{},"is_valid_reason"," gives the same GEOS messages as QGIS's validity check, so results agree with the ",[21,1426,1428],{"href":1427},"\u002Fspatial-data-processing-automation\u002Fdata-quality-and-topology-validation\u002Fcheck-geometry-validity-report-pyqgis\u002F","geometry validity report",". Comparing areas before and after ",[174,1431,1432],{},"make_valid"," flags repairs that changed a shape substantially, which deserve a look before they are written back with ",[174,1435,1047],{},". Whether to do this in shapely or with QGIS's own ",[174,1438,1439],{},"native:fixgeometries"," is mostly a matter of where the rest of the script lives — the GEOS repair underneath is the same.",[154,1442,1444],{"id":1443},"when-shapely-is-worth-the-round-trip","When shapely is worth the round trip",[14,1446,1447],{},"For single operations QGIS already offers, converting just to call the shapely version adds overhead and gains nothing. The conversion pays off when:",[159,1449,1450,1459,1465],{},[162,1451,1452,1455,1456,1458],{},[486,1453,1454],{},"The work is vectorised over many geometries"," — measures, predicates against one geometry, distance matrices — where shapely's array functions are much faster than a Python loop over ",[174,1457,90],{}," methods.",[162,1460,1461,1464],{},[486,1462,1463],{},"A library expects shapely"," — scikit-learn feature engineering, networkx graphs built from lines, osmnx, momepy morphology metrics.",[162,1466,1467,1470,1471,1474,1475,1478,1479,1482],{},[486,1468,1469],{},"You need a shapely-only function"," — ",[174,1472,1473],{},"shapely.voronoi_polygons"," with extents, ",[174,1476,1477],{},"shapely.coverage_simplify"," for topology-preserving simplification of coverages (shapely 2.1 with GEOS 3.12+), ",[174,1480,1481],{},"shapely.STRtree"," bulk queries.",[186,1484,1486],{"className":188,"code":1485,"language":190,"meta":191,"style":191},"tree = shapely.STRtree(geoms)\npairs = tree.query(geoms, predicate=\"intersects\")      # 2 × N array of index pairs\ntouching = pairs[:, pairs[0] != pairs[1]]\nprint(touching.shape[1] \u002F\u002F 2, \"pairs of intersecting parcels\")\n",[174,1487,1488,1498,1522,1548],{"__ignoreMap":191},[195,1489,1490,1493,1495],{"class":124,"line":197},[195,1491,1492],{"class":204},"tree ",[195,1494,310],{"class":200},[195,1496,1497],{"class":204}," shapely.STRtree(geoms)\n",[195,1499,1500,1503,1505,1508,1511,1513,1516,1519],{"class":124,"line":208},[195,1501,1502],{"class":204},"pairs ",[195,1504,310],{"class":200},[195,1506,1507],{"class":204}," tree.query(geoms, ",[195,1509,1510],{"class":345},"predicate",[195,1512,310],{"class":200},[195,1514,1515],{"class":380},"\"intersects\"",[195,1517,1518],{"class":204},")      ",[195,1520,1521],{"class":419},"# 2 × N array of index pairs\n",[195,1523,1524,1527,1529,1532,1534,1537,1540,1543,1545],{"class":124,"line":222},[195,1525,1526],{"class":204},"touching ",[195,1528,310],{"class":200},[195,1530,1531],{"class":204}," pairs[:, pairs[",[195,1533,46],{"class":254},[195,1535,1536],{"class":204},"] ",[195,1538,1539],{"class":200},"!=",[195,1541,1542],{"class":204}," pairs[",[195,1544,434],{"class":254},[195,1546,1547],{"class":204},"]]\n",[195,1549,1550,1552,1555,1557,1559,1562,1565,1567,1570],{"class":124,"line":229},[195,1551,401],{"class":254},[195,1553,1554],{"class":204},"(touching.shape[",[195,1556,434],{"class":254},[195,1558,1536],{"class":204},[195,1560,1561],{"class":200},"\u002F\u002F",[195,1563,1564],{"class":254}," 2",[195,1566,437],{"class":204},[195,1568,1569],{"class":380},"\"pairs of intersecting parcels\"",[195,1571,384],{"class":204},[14,1573,1574,489,1576,1579,1580,177],{},[486,1575,488],{},[174,1577,1578],{},"STRtree.query"," with an array of geometries returns all intersecting pairs in one call, a bulk operation with no direct QGIS equivalent short of a Python loop over a spatial index. Removing self-pairs and halving the count gives unique pairs. This is the kind of task — a whole-layer neighbour search — where the round trip is clearly worth it; compare the loop-based approach in ",[21,1581,1583],{"href":1582},"\u002Fspatial-data-processing-automation\u002Fdata-quality-and-topology-validation\u002Ffind-gaps-and-overlaps-between-polygons-pyqgis\u002F","finding gaps and overlaps",[154,1585,1587],{"id":1586},"qgis-version-compatibility","QGIS version compatibility",[14,1589,1590,1591,1594,1595,1598,1599,516,1602,1605,1606,177],{},"The WKB conversion works on QGIS 3.34 LTR, 3.40 LTR and QGIS 4. Shapely 2.0 or newer is required for the vectorised functions; ",[174,1592,1593],{},"coverage_simplify"," needs shapely 2.1 and GEOS 3.12. On QGIS 4, ",[174,1596,1597],{},"QgsAbstractGeometry.MaximumDifference"," is written ",[174,1600,1601],{},"QgsAbstractGeometry.SegmentationToleranceType.MaximumDifference",[174,1603,1604],{},"QgsField"," takes ",[174,1607,1608],{},"QMetaType.Type.Double",[154,1610,1612],{"id":1611},"troubleshooting","Troubleshooting",[159,1614,1615,1632,1638,1644],{},[162,1616,1617,1626,1627,1629,1630,177],{},[486,1618,1619,1622,1623,177],{},[174,1620,1621],{},"TypeError"," from ",[174,1624,1625],{},"from_wkb"," A ",[174,1628,496],{}," was passed; wrap it in ",[174,1631,500],{},[162,1633,1634,1637],{},[486,1635,1636],{},"Curves came out as straight lines between three points."," The geometry was not segmentized with a fine enough tolerance before conversion.",[162,1639,1640,1643],{},[486,1641,1642],{},"Results are in the wrong place after writing back."," A CRS transform happened on one side only; WKB never carries the CRS.",[162,1645,1646,1649],{},[486,1647,1648],{},"GEOS version warnings."," QGIS and shapely bundle different GEOS builds; results are compatible, but very new shapely functions may need a newer GEOS than QGIS ships.",[154,1651,1653],{"id":1652},"conclusion","Conclusion",[14,1655,1656,1657,1659],{},"Convert through WKB in both directions, batch whole layers into one ",[174,1658,1625],{}," call and use shapely's vectorised functions, write results back with single provider calls, segmentize curves and track the CRS yourself, and convert only when vectorisation, a library or a shapely-only function makes the trip worthwhile.",[154,1661,1663],{"id":1662},"frequently-asked-questions","Frequently Asked Questions",[14,1665,1666,1669],{},[486,1667,1668],{},"Is converting through WKT text good enough?","\nIt works, but WKT is slower and can lose precision through decimal formatting. Use WKB.",[14,1671,1672,1675],{},[486,1673,1674],{},"Can shapely and QGIS share memory?","\nNo. Each holds its own GEOS objects; conversion always copies, which is cheap through WKB.",[14,1677,1678,1681,1682,1685],{},[486,1679,1680],{},"Do GeoPandas geometries convert the same way?","\nYes. A GeoSeries holds shapely geometries; ",[174,1683,1684],{},"gdf.geometry.to_wkb()"," gives the bytes for QGIS.",[14,1687,1688,1691],{},[486,1689,1690],{},"Which library is faster for buffers?","\nThey call the same GEOS function. Speed differences come from looping in Python versus vectorised calls.",[154,1693,1695],{"id":1694},"related","Related",[159,1697,1698,1703,1709,1715,1721],{},[162,1699,1700,1702],{},[21,1701,24],{"href":23}," — the guide this recipe belongs to",[162,1704,1705],{},[21,1706,1708],{"href":1707},"\u002Fspatial-data-processing-automation\u002Fpyqgis-and-the-python-data-stack\u002Fconvert-layer-to-geopandas-geodataframe-pyqgis\u002F","Convert a QGIS Layer to a GeoPandas GeoDataFrame",[162,1710,1711],{},[21,1712,1714],{"href":1713},"\u002Fpyqgis-fundamentals-environment-setup\u002Ffeatures-geometries-and-memory-layers\u002Fcreate-geometry-from-wkt-and-geojson-pyqgis\u002F","Create Geometry from WKT and GeoJSON in PyQGIS",[162,1716,1717],{},[21,1718,1720],{"href":1719},"\u002Fpyqgis-fundamentals-environment-setup\u002Ffeatures-geometries-and-memory-layers\u002Fconvert-geometry-type-pyqgis\u002F","Convert Geometry Types in PyQGIS",[162,1722,1723],{},[21,1724,1726],{"href":1725},"\u002Fspatial-data-processing-automation\u002Fgeometry-operations-and-predicates\u002Fbuild-spatial-index-pyqgis\u002F","Build a Spatial Index in PyQGIS",[1728,1729,1730],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}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":191,"searchDepth":208,"depth":208,"links":1732},[1733,1734,1735,1736,1737,1738,1739,1740,1741,1742,1743,1744],{"id":156,"depth":208,"text":157},{"id":180,"depth":208,"text":181},{"id":525,"depth":208,"text":526},{"id":826,"depth":208,"text":827},{"id":1058,"depth":208,"text":1059},{"id":1248,"depth":208,"text":1249},{"id":1443,"depth":208,"text":1444},{"id":1586,"depth":208,"text":1587},{"id":1611,"depth":208,"text":1612},{"id":1652,"depth":208,"text":1653},{"id":1662,"depth":208,"text":1663},{"id":1694,"depth":208,"text":1695},"Pass geometries between QGIS and shapely without loss — WKB in both directions, vectorised conversion of whole layers, keeping Z values, handling empty and invalid shapes, and knowing when a shapely operation is worth the round trip.","md",{"slug":12,"type":1748,"breadcrumb":1749,"datePublished":1750,"dateModified":1750},"article","QgsGeometry and Shapely","2026-10-02","\u002Fspatial-data-processing-automation\u002Fpyqgis-and-the-python-data-stack\u002Fconvert-between-qgsgeometry-and-shapely",{"title":5,"description":1745},"spatial-data-processing-automation\u002Fpyqgis-and-the-python-data-stack\u002Fconvert-between-qgsgeometry-and-shapely\u002Findex","R1cuP_ytrc7uAQq96V4pOrsRvOtsWt8dX3cjZEs5Mkk",1790966264340]