[{"data":1,"prerenderedAt":1747},["ShallowReactive",2],{"doc:\u002Fspatial-data-processing-automation\u002Fdata-quality-and-topology-validation\u002Fsnap-geometries-to-layer-pyqgis":3},{"id":4,"title":5,"body":6,"description":1736,"extension":1737,"meta":1738,"navigation":204,"path":1743,"seo":1744,"stem":1745,"__hash__":1746},"docs\u002Fspatial-data-processing-automation\u002Fdata-quality-and-topology-validation\u002Fsnap-geometries-to-layer-pyqgis\u002Findex.md","Snap Geometries to a Layer in PyQGIS",{"type":7,"value":8,"toc":1721},"minimark",[9,13,17,31,134,139,157,161,164,382,389,393,396,503,515,649,654,658,661,767,781,785,788,867,876,880,883,1075,1085,1089,1092,1167,1430,1439,1443,1446,1579,1593,1597,1613,1617,1645,1649,1652,1656,1665,1671,1677,1683,1687,1717],[10,11,5],"h1",{"id":12},"snap-geometries-to-a-layer-in-pyqgis",[14,15,16],"p",{},"Snapping is how separately digitized geometries are made to agree. Parcels drawn against an old basemap are pulled onto the new road edges; a zoning layer is aligned with the parcel boundaries it is supposed to follow; a road network with undershoots is closed up; points captured by GPS are moved onto the pipes they describe. Done well, snapping removes slivers and gaps without anybody having to edit vertex by vertex. Done carelessly, it drags features metres out of place and creates new invalid geometries.",[14,18,19,20,25,26,30],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fspatial-data-processing-automation\u002Fdata-quality-and-topology-validation\u002F","Data Quality & Topology Validation",". It runs ",[27,28,29],"code",{},"native:snapgeometries"," with a deliberate tolerance and behaviour, snaps a layer to itself, snaps to a grid, and measures every change so the result can be trusted.",[14,32,33],{},[34,35,40,44,48,55,64,74,80,86,93,99,103,107,110,114,116,118,124,129],"svg",{"viewBox":36,"role":37,"ariaLabel":38,"xmlns":39},"0 0 760 246","img","Input vertices inside a tolerance band around a reference line move onto it, while vertices outside the band stay where they are","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[41,42,43],"title",{},"Moving vertices onto a reference",[45,46,47],"desc",{},"An input polygon edge runs slightly off a reference line. Within the tolerance band around the reference, each input vertex is moved onto the nearest reference vertex or onto the closest point of a reference segment, and vertices may be inserted where the reference has corners. Input vertices outside the band are left untouched.",[49,50],"rect",{"x":51,"y":51,"width":52,"height":53,"fill":54},"0","760","246","#f6f3ea",[56,57,63],"text",{"x":58,"y":59,"style":60,"fill":61,"textAnchor":62},"380","28","text-anchor:middle;font-size:14px;font-family:sans-serif;font-weight:bold","#17211d","middle","Only what is inside the tolerance moves",[49,65],{"x":66,"y":67,"width":68,"height":69,"rx":70,"fill":71,"stroke":72,"style":73},"60","108","640","44","4","#eef7f4","#0f766e","stroke-width:1",[75,76],"line",{"x1":66,"y1":77,"x2":78,"y2":77,"stroke":72,"style":79},"130","700","stroke-width:3",[56,81,85],{"x":78,"y":82,"style":83,"fill":72,"textAnchor":84},"100","text-anchor:end;font-size:10.5px;font-family:sans-serif","end","reference line",[87,88],"polyline",{"points":89,"fill":90,"stroke":91,"style":92},"70,146 200,140 330,148 460,120 590,138 690,200","none","#b45309","stroke-width:2.5",[94,95],"circle",{"cx":96,"cy":97,"r":98,"fill":91,"stroke":91,"style":73},"200","140","5",[94,100],{"cx":101,"cy":102,"r":98,"fill":91,"stroke":91,"style":73},"330","148",[94,104],{"cx":105,"cy":106,"r":98,"fill":91,"stroke":91,"style":73},"460","120",[94,108],{"cx":109,"cy":96,"r":98,"fill":91,"stroke":91,"style":73},"690",[75,111],{"x1":96,"y1":97,"x2":96,"y2":77,"stroke":112,"style":113},"#2f3b35","stroke-width:1.5",[75,115],{"x1":101,"y1":102,"x2":101,"y2":77,"stroke":112,"style":113},[75,117],{"x1":105,"y1":106,"x2":105,"y2":77,"stroke":112,"style":113},[56,119,123],{"x":58,"y":120,"style":121,"fill":122,"textAnchor":62},"92","text-anchor:middle;font-size:10.5px;font-family:sans-serif","#59645f","tolerance band",[56,125,128],{"x":126,"y":127,"style":121,"fill":91,"textAnchor":62},"600","226","outside the band: unchanged",[56,130,133],{"x":131,"y":132,"style":121,"fill":72,"textAnchor":62},"220","196","inside: moved onto the reference",[135,136,138],"h2",{"id":137},"prerequisites","Prerequisites",[140,141,142,146,149],"ul",{},[143,144,145],"li",{},"QGIS 3.34 LTR or newer, or the QGIS 4 series.",[143,147,148],{},"Input and reference layers in the same projected CRS. Snapping in degrees makes the tolerance meaningless.",[143,150,151,152,156],{},"Valid geometries in both. Check with a ",[21,153,155],{"href":154},"\u002Fspatial-data-processing-automation\u002Fdata-quality-and-topology-validation\u002Fcheck-geometry-validity-report-pyqgis\u002F","validity report"," first.",[135,158,160],{"id":159},"snap-one-layer-to-another","Snap one layer to another",[14,162,163],{},"The algorithm takes an input layer, a reference layer, a tolerance in layer units and a behaviour. Everything else follows from those four choices.",[165,166,171],"pre",{"className":167,"code":168,"language":169,"meta":170,"style":170},"language-python shiki shiki-themes github-dark","import processing\nfrom qgis.core import QgsProject\n\nzoning = QgsProject.instance().mapLayersByName(\"zoning\")[0]\nparcels = QgsProject.instance().mapLayersByName(\"parcels\")[0]\n\nresult = processing.run(\"native:snapgeometries\", {\n    \"INPUT\": zoning,\n    \"REFERENCE_LAYER\": parcels,\n    \"TOLERANCE\": 0.5,          # metres\n    \"BEHAVIOR\": 0,             # prefer aligning nodes, insert extra vertices\n    \"OUTPUT\": \"memory:zoning_snapped\",\n})\nsnapped = result[\"OUTPUT\"]\nQgsProject.instance().addMapLayer(snapped)\nprint(snapped.featureCount(), \"features snapped\")\n","python","",[27,172,173,185,199,206,231,250,255,272,281,290,309,325,339,345,361,367],{"__ignoreMap":170},[174,175,177,181],"span",{"class":75,"line":176},1,[174,178,180],{"class":179},"snl16","import",[174,182,184],{"class":183},"s95oV"," processing\n",[174,186,188,191,194,196],{"class":75,"line":187},2,[174,189,190],{"class":179},"from",[174,192,193],{"class":183}," qgis.core ",[174,195,180],{"class":179},[174,197,198],{"class":183}," QgsProject\n",[174,200,202],{"class":75,"line":201},3,[174,203,205],{"emptyLinePlaceholder":204},true,"\n",[174,207,209,212,215,218,222,225,228],{"class":75,"line":208},4,[174,210,211],{"class":183},"zoning ",[174,213,214],{"class":179},"=",[174,216,217],{"class":183}," QgsProject.instance().mapLayersByName(",[174,219,221],{"class":220},"sU2Wk","\"zoning\"",[174,223,224],{"class":183},")[",[174,226,51],{"class":227},"sDLfK",[174,229,230],{"class":183},"]\n",[174,232,234,237,239,241,244,246,248],{"class":75,"line":233},5,[174,235,236],{"class":183},"parcels ",[174,238,214],{"class":179},[174,240,217],{"class":183},[174,242,243],{"class":220},"\"parcels\"",[174,245,224],{"class":183},[174,247,51],{"class":227},[174,249,230],{"class":183},[174,251,253],{"class":75,"line":252},6,[174,254,205],{"emptyLinePlaceholder":204},[174,256,258,261,263,266,269],{"class":75,"line":257},7,[174,259,260],{"class":183},"result ",[174,262,214],{"class":179},[174,264,265],{"class":183}," processing.run(",[174,267,268],{"class":220},"\"native:snapgeometries\"",[174,270,271],{"class":183},", {\n",[174,273,275,278],{"class":75,"line":274},8,[174,276,277],{"class":220},"    \"INPUT\"",[174,279,280],{"class":183},": zoning,\n",[174,282,284,287],{"class":75,"line":283},9,[174,285,286],{"class":220},"    \"REFERENCE_LAYER\"",[174,288,289],{"class":183},": parcels,\n",[174,291,293,296,299,302,305],{"class":75,"line":292},10,[174,294,295],{"class":220},"    \"TOLERANCE\"",[174,297,298],{"class":183},": ",[174,300,301],{"class":227},"0.5",[174,303,304],{"class":183},",          ",[174,306,308],{"class":307},"sjoCn","# metres\n",[174,310,312,315,317,319,322],{"class":75,"line":311},11,[174,313,314],{"class":220},"    \"BEHAVIOR\"",[174,316,298],{"class":183},[174,318,51],{"class":227},[174,320,321],{"class":183},",             ",[174,323,324],{"class":307},"# prefer aligning nodes, insert extra vertices\n",[174,326,328,331,333,336],{"class":75,"line":327},12,[174,329,330],{"class":220},"    \"OUTPUT\"",[174,332,298],{"class":183},[174,334,335],{"class":220},"\"memory:zoning_snapped\"",[174,337,338],{"class":183},",\n",[174,340,342],{"class":75,"line":341},13,[174,343,344],{"class":183},"})\n",[174,346,348,351,353,356,359],{"class":75,"line":347},14,[174,349,350],{"class":183},"snapped ",[174,352,214],{"class":179},[174,354,355],{"class":183}," result[",[174,357,358],{"class":220},"\"OUTPUT\"",[174,360,230],{"class":183},[174,362,364],{"class":75,"line":363},15,[174,365,366],{"class":183},"QgsProject.instance().addMapLayer(snapped)\n",[174,368,370,373,376,379],{"class":75,"line":369},16,[174,371,372],{"class":227},"print",[174,374,375],{"class":183},"(snapped.featureCount(), ",[174,377,378],{"class":220},"\"features snapped\"",[174,380,381],{"class":183},")\n",[14,383,384,388],{},[385,386,387],"strong",{},"Breakdown:"," The output is a new layer; the input is untouched, which is what you want until the result has been checked. The tolerance is the maximum distance a vertex may move, in the layer's units. Behaviour 0 first tries to move each input vertex onto a reference vertex, falls back to the closest point on a reference segment, and inserts vertices into the input where the reference has corners inside the tolerance — so a zoning edge that should follow a parcel boundary acquires the parcel's corners. Feature counts and attributes are unchanged; only geometry moves.",[135,390,392],{"id":391},"choose-the-behaviour","Choose the behaviour",[14,394,395],{},"The behaviours trade fidelity to the reference against minimal change to the input. Picking the wrong one is the commonest reason snapping disappoints.",[14,397,398],{},[34,399,402,405,408,411,414,422,428,432,436,441,444,447,450,454,458,461,464,470,476,480,484,488,492,495,498],{"viewBox":400,"role":37,"ariaLabel":401,"xmlns":39},"0 0 760 290","The snapping behaviours grouped by what they move: all vertices with or without inserted vertices, line end points only, end points to end points, and anchor nodes",[41,403,404],{},"Snapping behaviours compared",[45,406,407],{},"Behaviours 0 and 1 snap every vertex and insert extra vertices so the input follows the reference shape; 0 prefers reference vertices and 1 the closest point. Behaviours 2 and 3 do the same without inserting vertices, keeping vertex counts. Behaviours 4 and 5 move only line end points, which suits closing undershoots in networks. Behaviour 6 snaps end points only to reference end points. Behaviour 7 snaps to anchor nodes and is used when snapping a layer to itself.",[49,409],{"x":51,"y":51,"width":52,"height":410,"fill":54},"290",[56,412,413],{"x":58,"y":59,"style":60,"fill":61,"textAnchor":62},"What moves, and where to",[49,415],{"x":416,"y":417,"width":418,"height":419,"rx":420,"fill":71,"stroke":72,"style":421},"24","52","222","104","8","stroke-width:2",[56,423,427],{"x":424,"y":425,"style":426,"fill":72,"textAnchor":62},"135","83.78","text-anchor:middle;font-size:11.5px;font-family:sans-serif;font-weight:bold","0 · 1",[56,429,431],{"x":424,"y":430,"style":121,"fill":112,"textAnchor":62},"107.78","all vertices + insert",[56,433,435],{"x":424,"y":434,"style":121,"fill":122,"textAnchor":62},"131.78","follow the reference",[49,437],{"x":438,"y":417,"width":418,"height":419,"rx":420,"fill":439,"stroke":440,"style":421},"269","#eff3ff","#2563eb",[56,442,443],{"x":58,"y":425,"style":426,"fill":440,"textAnchor":62},"2 · 3",[56,445,446],{"x":58,"y":430,"style":121,"fill":112,"textAnchor":62},"all vertices, no insert",[56,448,449],{"x":58,"y":434,"style":121,"fill":122,"textAnchor":62},"keep vertex count",[49,451],{"x":452,"y":417,"width":418,"height":419,"rx":420,"fill":453,"stroke":91,"style":421},"514","#fdf2e2",[56,455,457],{"x":456,"y":425,"style":426,"fill":91,"textAnchor":62},"625","4 · 5",[56,459,460],{"x":456,"y":430,"style":121,"fill":112,"textAnchor":62},"line end points only",[56,462,463],{"x":456,"y":434,"style":121,"fill":122,"textAnchor":62},"close undershoots",[49,465],{"x":416,"y":466,"width":467,"height":120,"rx":420,"fill":468,"stroke":469,"style":421},"172","340","#e8efe6","#15803d",[56,471,475],{"x":472,"y":473,"style":426,"fill":474,"textAnchor":62},"194","197.78","#166534","6",[56,477,479],{"x":472,"y":478,"style":121,"fill":112,"textAnchor":62},"221.78","end points → end points",[56,481,483],{"x":472,"y":482,"style":121,"fill":122,"textAnchor":62},"245.78","join line networks",[49,485],{"x":486,"y":466,"width":467,"height":120,"rx":420,"fill":487,"stroke":122,"style":421},"396","#fffdf7",[56,489,491],{"x":490,"y":473,"style":426,"fill":61,"textAnchor":62},"566","7",[56,493,494],{"x":490,"y":478,"style":121,"fill":112,"textAnchor":62},"anchor nodes",[56,496,497],{"x":490,"y":482,"style":121,"fill":122,"textAnchor":62},"snap a layer to itself",[56,499,502],{"x":58,"y":500,"style":501,"fill":122,"textAnchor":62},"282","text-anchor:middle;font-size:10.0px;font-family:sans-serif","even numbers prefer reference vertices, odd prefer the closest point",[14,504,505,506,510,511,514],{},"The even\u002Fodd split is the subtle part: \"prefer aligning nodes\" moves a vertex onto the nearest ",[507,508,509],"em",{},"reference vertex"," if one is within tolerance, even if a reference ",[507,512,513],{},"segment"," is closer; \"prefer closest point\" moves it to the closest location on the reference, vertex or not. For boundaries that should share corners exactly — zoning on parcels — prefer nodes. For points or lines that should simply lie on the reference — GPS points onto pipes — prefer the closest point.",[165,516,518],{"className":167,"code":517,"language":169,"meta":170,"style":170},"for behaviour in (0, 1, 2, 3):\n    out = processing.run(\"native:snapgeometries\", {\n        \"INPUT\": zoning, \"REFERENCE_LAYER\": parcels, \"TOLERANCE\": 0.5,\n        \"BEHAVIOR\": behaviour, \"OUTPUT\": \"memory:\"})[\"OUTPUT\"]\n    vertices = sum(f.geometry().constGet().nCoordinates() for f in out.getFeatures())\n    print(behaviour, vertices, \"vertices\")\n",[27,519,520,555,568,591,613,636],{"__ignoreMap":170},[174,521,522,525,528,531,534,536,539,542,544,547,549,552],{"class":75,"line":176},[174,523,524],{"class":179},"for",[174,526,527],{"class":183}," behaviour ",[174,529,530],{"class":179},"in",[174,532,533],{"class":183}," (",[174,535,51],{"class":227},[174,537,538],{"class":183},", ",[174,540,541],{"class":227},"1",[174,543,538],{"class":183},[174,545,546],{"class":227},"2",[174,548,538],{"class":183},[174,550,551],{"class":227},"3",[174,553,554],{"class":183},"):\n",[174,556,557,560,562,564,566],{"class":75,"line":187},[174,558,559],{"class":183},"    out ",[174,561,214],{"class":179},[174,563,265],{"class":183},[174,565,268],{"class":220},[174,567,271],{"class":183},[174,569,570,573,576,579,582,585,587,589],{"class":75,"line":201},[174,571,572],{"class":220},"        \"INPUT\"",[174,574,575],{"class":183},": zoning, ",[174,577,578],{"class":220},"\"REFERENCE_LAYER\"",[174,580,581],{"class":183},": parcels, ",[174,583,584],{"class":220},"\"TOLERANCE\"",[174,586,298],{"class":183},[174,588,301],{"class":227},[174,590,338],{"class":183},[174,592,593,596,599,601,603,606,609,611],{"class":75,"line":208},[174,594,595],{"class":220},"        \"BEHAVIOR\"",[174,597,598],{"class":183},": behaviour, ",[174,600,358],{"class":220},[174,602,298],{"class":183},[174,604,605],{"class":220},"\"memory:\"",[174,607,608],{"class":183},"})[",[174,610,358],{"class":220},[174,612,230],{"class":183},[174,614,615,618,620,623,626,628,631,633],{"class":75,"line":233},[174,616,617],{"class":183},"    vertices ",[174,619,214],{"class":179},[174,621,622],{"class":227}," sum",[174,624,625],{"class":183},"(f.geometry().constGet().nCoordinates() ",[174,627,524],{"class":179},[174,629,630],{"class":183}," f ",[174,632,530],{"class":179},[174,634,635],{"class":183}," out.getFeatures())\n",[174,637,638,641,644,647],{"class":75,"line":252},[174,639,640],{"class":227},"    print",[174,642,643],{"class":183},"(behaviour, vertices, ",[174,645,646],{"class":220},"\"vertices\"",[174,648,381],{"class":183},[14,650,651,653],{},[385,652,387],{}," Comparing vertex counts across behaviours shows how much each one reshapes the input. Behaviours that insert vertices can multiply vertex counts where the reference is detailed; that is the price of following the reference closely. Running all candidates on a sample and looking at the results is quicker than reasoning about them in the abstract.",[135,655,657],{"id":656},"snap-a-layer-to-itself","Snap a layer to itself",[14,659,660],{},"Slivers and undershoots inside a single layer are fixed by using the layer as its own reference. Behaviour 7 was designed for this: it snaps vertices to \"anchor\" nodes so that neighbouring polygons converge on shared vertices instead of chasing each other.",[165,662,664],{"className":167,"code":663,"language":169,"meta":170,"style":170},"self_snapped = processing.run(\"native:snapgeometries\", {\n    \"INPUT\": parcels, \"REFERENCE_LAYER\": parcels,\n    \"TOLERANCE\": 0.2, \"BEHAVIOR\": 7,\n    \"OUTPUT\": \"memory:parcels_self_snapped\"})[\"OUTPUT\"]\n\nfixed = processing.run(\"native:fixgeometries\", {\n    \"INPUT\": self_snapped, \"OUTPUT\": \"memory:parcels_clean\"})[\"OUTPUT\"]\nQgsProject.instance().addMapLayer(fixed)\n",[27,665,666,679,689,709,724,728,742,762],{"__ignoreMap":170},[174,667,668,671,673,675,677],{"class":75,"line":176},[174,669,670],{"class":183},"self_snapped ",[174,672,214],{"class":179},[174,674,265],{"class":183},[174,676,268],{"class":220},[174,678,271],{"class":183},[174,680,681,683,685,687],{"class":75,"line":187},[174,682,277],{"class":220},[174,684,581],{"class":183},[174,686,578],{"class":220},[174,688,289],{"class":183},[174,690,691,693,695,698,700,703,705,707],{"class":75,"line":201},[174,692,295],{"class":220},[174,694,298],{"class":183},[174,696,697],{"class":227},"0.2",[174,699,538],{"class":183},[174,701,702],{"class":220},"\"BEHAVIOR\"",[174,704,298],{"class":183},[174,706,491],{"class":227},[174,708,338],{"class":183},[174,710,711,713,715,718,720,722],{"class":75,"line":208},[174,712,330],{"class":220},[174,714,298],{"class":183},[174,716,717],{"class":220},"\"memory:parcels_self_snapped\"",[174,719,608],{"class":183},[174,721,358],{"class":220},[174,723,230],{"class":183},[174,725,726],{"class":75,"line":233},[174,727,205],{"emptyLinePlaceholder":204},[174,729,730,733,735,737,740],{"class":75,"line":252},[174,731,732],{"class":183},"fixed ",[174,734,214],{"class":179},[174,736,265],{"class":183},[174,738,739],{"class":220},"\"native:fixgeometries\"",[174,741,271],{"class":183},[174,743,744,746,749,751,753,756,758,760],{"class":75,"line":257},[174,745,277],{"class":220},[174,747,748],{"class":183},": self_snapped, ",[174,750,358],{"class":220},[174,752,298],{"class":183},[174,754,755],{"class":220},"\"memory:parcels_clean\"",[174,757,608],{"class":183},[174,759,358],{"class":220},[174,761,230],{"class":183},[174,763,764],{"class":75,"line":274},[174,765,766],{"class":183},"QgsProject.instance().addMapLayer(fixed)\n",[14,768,769,771,772,776,777,780],{},[385,770,387],{}," Self-snapping with a small tolerance pulls nearly coincident vertices of adjacent polygons together, closing the slivers that ",[21,773,775],{"href":774},"\u002Fspatial-data-processing-automation\u002Fdata-quality-and-topology-validation\u002Ffind-gaps-and-overlaps-between-polygons-pyqgis\u002F","gap and overlap checks"," report as noise. Snapping can collapse a narrow polygon or make a ring touch itself, so running ",[27,778,779],{},"native:fixgeometries"," afterwards is standard practice. Use the smallest tolerance that closes the slivers — read it off the widths of the slivers you found — because a tolerance close to the size of real features starts merging them.",[135,782,784],{"id":783},"snap-to-a-grid","Snap to a grid",[14,786,787],{},"Sometimes the reference is not a layer but a precision. Coordinates with spurious decimals — from reprojection, from CAD exports, from floating-point arithmetic — make exact comparisons fail. Snapping to a grid rounds every vertex to a fixed spacing.",[165,789,791],{"className":167,"code":790,"language":169,"meta":170,"style":170},"gridded = processing.run(\"native:snappointstogrid\", {\n    \"INPUT\": parcels, \"HSPACING\": 0.001, \"VSPACING\": 0.001,\n    \"ZSPACING\": 0, \"MSPACING\": 0,\n    \"OUTPUT\": \"memory:parcels_mm\"})[\"OUTPUT\"]\n",[27,792,793,807,832,852],{"__ignoreMap":170},[174,794,795,798,800,802,805],{"class":75,"line":176},[174,796,797],{"class":183},"gridded ",[174,799,214],{"class":179},[174,801,265],{"class":183},[174,803,804],{"class":220},"\"native:snappointstogrid\"",[174,806,271],{"class":183},[174,808,809,811,813,816,818,821,823,826,828,830],{"class":75,"line":187},[174,810,277],{"class":220},[174,812,581],{"class":183},[174,814,815],{"class":220},"\"HSPACING\"",[174,817,298],{"class":183},[174,819,820],{"class":227},"0.001",[174,822,538],{"class":183},[174,824,825],{"class":220},"\"VSPACING\"",[174,827,298],{"class":183},[174,829,820],{"class":227},[174,831,338],{"class":183},[174,833,834,837,839,841,843,846,848,850],{"class":75,"line":201},[174,835,836],{"class":220},"    \"ZSPACING\"",[174,838,298],{"class":183},[174,840,51],{"class":227},[174,842,538],{"class":183},[174,844,845],{"class":220},"\"MSPACING\"",[174,847,298],{"class":183},[174,849,51],{"class":227},[174,851,338],{"class":183},[174,853,854,856,858,861,863,865],{"class":75,"line":208},[174,855,330],{"class":220},[174,857,298],{"class":183},[174,859,860],{"class":220},"\"memory:parcels_mm\"",[174,862,608],{"class":183},[174,864,358],{"class":220},[174,866,230],{"class":183},[14,868,869,871,872,875],{},[385,870,387],{}," A millimetre grid is invisible at any map scale but makes coordinates that should be identical actually identical, which helps exact-duplicate detection, end-point matching in networks and joins on geometry. Grid snapping can create duplicate consecutive vertices or collapse tiny segments; ",[27,873,874],{},"native:removeduplicatevertices"," cleans those. Zero Z and M spacing leaves those values alone.",[135,877,879],{"id":878},"snap-points-onto-lines","Snap points onto lines",[14,881,882],{},"Field data often records assets as points that should lie on a line layer: valves on water mains, signs along roads, sample sites on a river centreline. GPS error leaves them a metre or two off. Snapping with the closest-point behaviour moves each point onto the nearest line within tolerance, and points further away than the tolerance are left alone and can be reviewed separately.",[165,884,886],{"className":167,"code":885,"language":169,"meta":170,"style":170},"valves = QgsProject.instance().mapLayersByName(\"valves_gps\")[0]\nmains = QgsProject.instance().mapLayersByName(\"water_mains\")[0]\n\non_line = processing.run(\"native:snapgeometries\", {\n    \"INPUT\": valves, \"REFERENCE_LAYER\": mains,\n    \"TOLERANCE\": 3.0, \"BEHAVIOR\": 1,      # closest point on the reference\n    \"OUTPUT\": \"memory:valves_on_mains\"})[\"OUTPUT\"]\n\noriginal = {f[\"valve_id\"]: f.geometry() for f in valves.getFeatures()}\nunmoved = [f[\"valve_id\"] for f in on_line.getFeatures()\n           if f.geometry().equals(original[f[\"valve_id\"]])]\nprint(len(unmoved), \"valves further than 3 m from any main\")\n",[27,887,888,906,924,928,941,953,976,991,995,1020,1044,1057],{"__ignoreMap":170},[174,889,890,893,895,897,900,902,904],{"class":75,"line":176},[174,891,892],{"class":183},"valves ",[174,894,214],{"class":179},[174,896,217],{"class":183},[174,898,899],{"class":220},"\"valves_gps\"",[174,901,224],{"class":183},[174,903,51],{"class":227},[174,905,230],{"class":183},[174,907,908,911,913,915,918,920,922],{"class":75,"line":187},[174,909,910],{"class":183},"mains ",[174,912,214],{"class":179},[174,914,217],{"class":183},[174,916,917],{"class":220},"\"water_mains\"",[174,919,224],{"class":183},[174,921,51],{"class":227},[174,923,230],{"class":183},[174,925,926],{"class":75,"line":201},[174,927,205],{"emptyLinePlaceholder":204},[174,929,930,933,935,937,939],{"class":75,"line":208},[174,931,932],{"class":183},"on_line ",[174,934,214],{"class":179},[174,936,265],{"class":183},[174,938,268],{"class":220},[174,940,271],{"class":183},[174,942,943,945,948,950],{"class":75,"line":233},[174,944,277],{"class":220},[174,946,947],{"class":183},": valves, ",[174,949,578],{"class":220},[174,951,952],{"class":183},": mains,\n",[174,954,955,957,959,962,964,966,968,970,973],{"class":75,"line":252},[174,956,295],{"class":220},[174,958,298],{"class":183},[174,960,961],{"class":227},"3.0",[174,963,538],{"class":183},[174,965,702],{"class":220},[174,967,298],{"class":183},[174,969,541],{"class":227},[174,971,972],{"class":183},",      ",[174,974,975],{"class":307},"# closest point on the reference\n",[174,977,978,980,982,985,987,989],{"class":75,"line":257},[174,979,330],{"class":220},[174,981,298],{"class":183},[174,983,984],{"class":220},"\"memory:valves_on_mains\"",[174,986,608],{"class":183},[174,988,358],{"class":220},[174,990,230],{"class":183},[174,992,993],{"class":75,"line":274},[174,994,205],{"emptyLinePlaceholder":204},[174,996,997,1000,1002,1005,1008,1011,1013,1015,1017],{"class":75,"line":283},[174,998,999],{"class":183},"original ",[174,1001,214],{"class":179},[174,1003,1004],{"class":183}," {f[",[174,1006,1007],{"class":220},"\"valve_id\"",[174,1009,1010],{"class":183},"]: f.geometry() ",[174,1012,524],{"class":179},[174,1014,630],{"class":183},[174,1016,530],{"class":179},[174,1018,1019],{"class":183}," valves.getFeatures()}\n",[174,1021,1022,1025,1027,1030,1032,1035,1037,1039,1041],{"class":75,"line":292},[174,1023,1024],{"class":183},"unmoved ",[174,1026,214],{"class":179},[174,1028,1029],{"class":183}," [f[",[174,1031,1007],{"class":220},[174,1033,1034],{"class":183},"] ",[174,1036,524],{"class":179},[174,1038,630],{"class":183},[174,1040,530],{"class":179},[174,1042,1043],{"class":183}," on_line.getFeatures()\n",[174,1045,1046,1049,1052,1054],{"class":75,"line":311},[174,1047,1048],{"class":179},"           if",[174,1050,1051],{"class":183}," f.geometry().equals(original[f[",[174,1053,1007],{"class":220},[174,1055,1056],{"class":183},"]])]\n",[174,1058,1059,1061,1064,1067,1070,1073],{"class":75,"line":327},[174,1060,372],{"class":227},[174,1062,1063],{"class":183},"(",[174,1065,1066],{"class":227},"len",[174,1068,1069],{"class":183},"(unmoved), ",[174,1071,1072],{"class":220},"\"valves further than 3 m from any main\"",[174,1074,381],{"class":183},[14,1076,1077,1079,1080,1084],{},[385,1078,387],{}," For points there are no segments to align, so the difference between behaviours reduces to \"nearest reference vertex\" versus \"closest point anywhere on the line\"; the closest point is almost always what field data needs. Points that did not move were outside the tolerance — either genuinely off the network, or the network is incomplete there — and deserve a look rather than a bigger tolerance. The ",[21,1081,1083],{"href":1082},"\u002Fspatial-data-processing-automation\u002Fnetwork-analysis-and-routing\u002Fsnap-points-to-network-pyqgis\u002F","network snapping recipe"," covers the related case of snapping origins and destinations before routing, where the snapped location also splits a network edge.",[135,1086,1088],{"id":1087},"measure-what-moved","Measure what moved",[14,1090,1091],{},"Snapping should never be a black box. Comparing each feature's geometry before and after gives the maximum displacement and the change in area, so an unexpectedly large move is caught before the result replaces the original.",[14,1093,1094],{},[34,1095,1098,1101,1104,1107,1120,1123,1125,1129,1133,1137,1139,1142,1145,1148,1150,1153,1156,1159,1163],{"viewBox":1096,"role":37,"ariaLabel":1097,"xmlns":39},"0 0 760 216","Per-feature audit of snapping: Hausdorff distance for the largest displacement and relative area change, with features beyond limits flagged for review",[41,1099,1100],{},"Before-and-after audit",[45,1102,1103],{},"For each feature the audit compares the original and snapped geometry: the Hausdorff distance gives the largest displacement of any part of the shape, and the relative area change shows whether the shape grew or shrank. Features whose displacement exceeds the tolerance, or whose area changed by more than a set percentage, are flagged for review instead of being accepted automatically.",[49,1105],{"x":51,"y":51,"width":52,"height":1106,"fill":54},"216",[1108,1109,1110],"defs",{},[1111,1112,1116],"marker",{"id":1113,"viewBox":1114,"refX":420,"refY":98,"markerWidth":491,"markerHeight":491,"orient":1115},"snapAuditArrow","0 0 10 10","auto-start-reverse",[1117,1118],"path",{"d":1119,"fill":112},"M0 0 L10 5 L0 10 z",[56,1121,1122],{"x":58,"y":59,"style":60,"fill":61,"textAnchor":62},"Accept, or look again",[49,1124],{"x":416,"y":66,"width":418,"height":97,"rx":420,"fill":71,"stroke":72,"style":421},[56,1126,1128],{"x":424,"y":1127,"style":426,"fill":72,"textAnchor":62},"105.78","Hausdorff distance",[56,1130,1132],{"x":424,"y":1131,"style":121,"fill":112,"textAnchor":62},"133.78","largest move",[56,1134,1136],{"x":424,"y":1135,"style":121,"fill":122,"textAnchor":62},"161.78","should be ≤ tolerance",[49,1138],{"x":438,"y":66,"width":418,"height":97,"rx":420,"fill":439,"stroke":440,"style":421},[56,1140,1141],{"x":58,"y":1127,"style":426,"fill":440,"textAnchor":62},"area change",[56,1143,1144],{"x":58,"y":1131,"style":121,"fill":112,"textAnchor":62},"relative %",[56,1146,1147],{"x":58,"y":1135,"style":121,"fill":122,"textAnchor":62},"should be small",[49,1149],{"x":452,"y":66,"width":418,"height":97,"rx":420,"fill":453,"stroke":91,"style":421},[56,1151,1152],{"x":456,"y":1127,"style":426,"fill":91,"textAnchor":62},"flag for review",[56,1154,1155],{"x":456,"y":1131,"style":121,"fill":112,"textAnchor":62},"beyond either limit",[56,1157,1158],{"x":456,"y":1135,"style":121,"fill":122,"textAnchor":62},"don't auto-accept",[75,1160],{"x1":53,"y1":77,"x2":1161,"y2":77,"stroke":112,"style":1162},"265","stroke-width:1.8;marker-end:url(#snapAuditArrow)",[75,1164],{"x1":1165,"y1":77,"x2":1166,"y2":77,"stroke":112,"style":1162},"491","510",[165,1168,1170],{"className":167,"code":1169,"language":169,"meta":170,"style":170},"from qgis.core import QgsFeatureRequest\n\nbefore = {f[\"parcel_id\"]: f.geometry() for f in parcels.getFeatures()}\nflags = []\nfor f in fixed.getFeatures():\n    old = before.get(f[\"parcel_id\"])\n    if old is None:\n        continue\n    new = f.geometry()\n    moved = old.hausdorffDistance(new)\n    area_change = abs(new.area() - old.area()) \u002F old.area() if old.area() else 0\n    if moved > 0.2 or area_change > 0.005:\n        flags.append((f[\"parcel_id\"], round(moved, 3), round(area_change * 100, 3)))\nprint(len(flags), \"parcels moved more than expected\")\nfor row in flags[:10]:\n    print(row)\n",[27,1171,1172,1183,1187,1210,1220,1231,1246,1263,1268,1278,1288,1324,1350,1389,1405,1423],{"__ignoreMap":170},[174,1173,1174,1176,1178,1180],{"class":75,"line":176},[174,1175,190],{"class":179},[174,1177,193],{"class":183},[174,1179,180],{"class":179},[174,1181,1182],{"class":183}," QgsFeatureRequest\n",[174,1184,1185],{"class":75,"line":187},[174,1186,205],{"emptyLinePlaceholder":204},[174,1188,1189,1192,1194,1196,1199,1201,1203,1205,1207],{"class":75,"line":201},[174,1190,1191],{"class":183},"before ",[174,1193,214],{"class":179},[174,1195,1004],{"class":183},[174,1197,1198],{"class":220},"\"parcel_id\"",[174,1200,1010],{"class":183},[174,1202,524],{"class":179},[174,1204,630],{"class":183},[174,1206,530],{"class":179},[174,1208,1209],{"class":183}," parcels.getFeatures()}\n",[174,1211,1212,1215,1217],{"class":75,"line":208},[174,1213,1214],{"class":183},"flags ",[174,1216,214],{"class":179},[174,1218,1219],{"class":183}," []\n",[174,1221,1222,1224,1226,1228],{"class":75,"line":233},[174,1223,524],{"class":179},[174,1225,630],{"class":183},[174,1227,530],{"class":179},[174,1229,1230],{"class":183}," fixed.getFeatures():\n",[174,1232,1233,1236,1238,1241,1243],{"class":75,"line":252},[174,1234,1235],{"class":183},"    old ",[174,1237,214],{"class":179},[174,1239,1240],{"class":183}," before.get(f[",[174,1242,1198],{"class":220},[174,1244,1245],{"class":183},"])\n",[174,1247,1248,1251,1254,1257,1260],{"class":75,"line":257},[174,1249,1250],{"class":179},"    if",[174,1252,1253],{"class":183}," old ",[174,1255,1256],{"class":179},"is",[174,1258,1259],{"class":227}," None",[174,1261,1262],{"class":183},":\n",[174,1264,1265],{"class":75,"line":274},[174,1266,1267],{"class":179},"        continue\n",[174,1269,1270,1273,1275],{"class":75,"line":283},[174,1271,1272],{"class":183},"    new ",[174,1274,214],{"class":179},[174,1276,1277],{"class":183}," f.geometry()\n",[174,1279,1280,1283,1285],{"class":75,"line":292},[174,1281,1282],{"class":183},"    moved ",[174,1284,214],{"class":179},[174,1286,1287],{"class":183}," old.hausdorffDistance(new)\n",[174,1289,1290,1293,1295,1298,1301,1304,1307,1310,1313,1316,1318,1321],{"class":75,"line":311},[174,1291,1292],{"class":183},"    area_change ",[174,1294,214],{"class":179},[174,1296,1297],{"class":227}," abs",[174,1299,1300],{"class":183},"(new.area() ",[174,1302,1303],{"class":179},"-",[174,1305,1306],{"class":183}," old.area()) ",[174,1308,1309],{"class":179},"\u002F",[174,1311,1312],{"class":183}," old.area() ",[174,1314,1315],{"class":179},"if",[174,1317,1312],{"class":183},[174,1319,1320],{"class":179},"else",[174,1322,1323],{"class":227}," 0\n",[174,1325,1326,1328,1331,1334,1337,1340,1343,1345,1348],{"class":75,"line":327},[174,1327,1250],{"class":179},[174,1329,1330],{"class":183}," moved ",[174,1332,1333],{"class":179},">",[174,1335,1336],{"class":227}," 0.2",[174,1338,1339],{"class":179}," or",[174,1341,1342],{"class":183}," area_change ",[174,1344,1333],{"class":179},[174,1346,1347],{"class":227}," 0.005",[174,1349,1262],{"class":183},[174,1351,1352,1355,1357,1360,1363,1366,1368,1371,1373,1376,1379,1382,1384,1386],{"class":75,"line":341},[174,1353,1354],{"class":183},"        flags.append((f[",[174,1356,1198],{"class":220},[174,1358,1359],{"class":183},"], ",[174,1361,1362],{"class":227},"round",[174,1364,1365],{"class":183},"(moved, ",[174,1367,551],{"class":227},[174,1369,1370],{"class":183},"), ",[174,1372,1362],{"class":227},[174,1374,1375],{"class":183},"(area_change ",[174,1377,1378],{"class":179},"*",[174,1380,1381],{"class":227}," 100",[174,1383,538],{"class":183},[174,1385,551],{"class":227},[174,1387,1388],{"class":183},")))\n",[174,1390,1391,1393,1395,1397,1400,1403],{"class":75,"line":347},[174,1392,372],{"class":227},[174,1394,1063],{"class":183},[174,1396,1066],{"class":227},[174,1398,1399],{"class":183},"(flags), ",[174,1401,1402],{"class":220},"\"parcels moved more than expected\"",[174,1404,381],{"class":183},[174,1406,1407,1409,1412,1414,1417,1420],{"class":75,"line":363},[174,1408,524],{"class":179},[174,1410,1411],{"class":183}," row ",[174,1413,530],{"class":179},[174,1415,1416],{"class":183}," flags[:",[174,1418,1419],{"class":227},"10",[174,1421,1422],{"class":183},"]:\n",[174,1424,1425,1427],{"class":75,"line":369},[174,1426,640],{"class":227},[174,1428,1429],{"class":183},"(row)\n",[14,1431,1432,1434,1435,1438],{},[385,1433,387],{}," The Hausdorff distance is the largest distance from any point of one geometry to the other — a direct measure of the worst displacement. It should never exceed the tolerance on a simple snap; when it does, ",[27,1436,1437],{},"fixgeometries"," reshaped the feature substantially, which deserves a look. The relative area change catches the opposite case: a small move that still changes a narrow parcel's area noticeably. Matching features by a business key rather than feature id keeps the comparison correct even if the output layer renumbered features.",[135,1440,1442],{"id":1441},"replace-the-original-safely","Replace the original safely",[14,1444,1445],{},"When the audit is clean, write the snapped geometries back. Updating geometry in place keeps feature ids, relations and styling intact.",[165,1447,1449],{"className":167,"code":1448,"language":169,"meta":170,"style":170},"from qgis.core import edit\n\nnew_geoms = {f[\"parcel_id\"]: f.geometry() for f in fixed.getFeatures()}\nflagged = {pid for pid, *_ in flags}\nwith edit(parcels):\n    for f in parcels.getFeatures():\n        pid = f[\"parcel_id\"]\n        if pid in new_geoms and pid not in flagged:\n            parcels.changeGeometry(f.id(), new_geoms[pid])\n",[27,1450,1451,1462,1466,1488,1513,1521,1533,1547,1574],{"__ignoreMap":170},[174,1452,1453,1455,1457,1459],{"class":75,"line":176},[174,1454,190],{"class":179},[174,1456,193],{"class":183},[174,1458,180],{"class":179},[174,1460,1461],{"class":183}," edit\n",[174,1463,1464],{"class":75,"line":187},[174,1465,205],{"emptyLinePlaceholder":204},[174,1467,1468,1471,1473,1475,1477,1479,1481,1483,1485],{"class":75,"line":201},[174,1469,1470],{"class":183},"new_geoms ",[174,1472,214],{"class":179},[174,1474,1004],{"class":183},[174,1476,1198],{"class":220},[174,1478,1010],{"class":183},[174,1480,524],{"class":179},[174,1482,630],{"class":183},[174,1484,530],{"class":179},[174,1486,1487],{"class":183}," fixed.getFeatures()}\n",[174,1489,1490,1493,1495,1498,1500,1503,1505,1508,1510],{"class":75,"line":208},[174,1491,1492],{"class":183},"flagged ",[174,1494,214],{"class":179},[174,1496,1497],{"class":183}," {pid ",[174,1499,524],{"class":179},[174,1501,1502],{"class":183}," pid, ",[174,1504,1378],{"class":179},[174,1506,1507],{"class":183},"_ ",[174,1509,530],{"class":179},[174,1511,1512],{"class":183}," flags}\n",[174,1514,1515,1518],{"class":75,"line":233},[174,1516,1517],{"class":179},"with",[174,1519,1520],{"class":183}," edit(parcels):\n",[174,1522,1523,1526,1528,1530],{"class":75,"line":252},[174,1524,1525],{"class":179},"    for",[174,1527,630],{"class":183},[174,1529,530],{"class":179},[174,1531,1532],{"class":183}," parcels.getFeatures():\n",[174,1534,1535,1538,1540,1543,1545],{"class":75,"line":257},[174,1536,1537],{"class":183},"        pid ",[174,1539,214],{"class":179},[174,1541,1542],{"class":183}," f[",[174,1544,1198],{"class":220},[174,1546,230],{"class":183},[174,1548,1549,1552,1555,1557,1560,1563,1565,1568,1571],{"class":75,"line":274},[174,1550,1551],{"class":179},"        if",[174,1553,1554],{"class":183}," pid ",[174,1556,530],{"class":179},[174,1558,1559],{"class":183}," new_geoms ",[174,1561,1562],{"class":179},"and",[174,1564,1554],{"class":183},[174,1566,1567],{"class":179},"not",[174,1569,1570],{"class":179}," in",[174,1572,1573],{"class":183}," flagged:\n",[174,1575,1576],{"class":75,"line":283},[174,1577,1578],{"class":183},"            parcels.changeGeometry(f.id(), new_geoms[pid])\n",[14,1580,1581,1583,1584,1587,1588,1592],{},[385,1582,387],{}," Writing geometries back rather than replacing the layer preserves everything attached to it — joins, relations, form configuration, edit history. Flagged features keep their old geometry until someone has reviewed them. Do this inside ",[27,1585,1586],{},"edit()"," so a failure leaves the original untouched; for interactive work, wrapping it in an edit command makes the whole snap undoable in one step, as shown in ",[21,1589,1591],{"href":1590},"\u002Fpyqgis-fundamentals-environment-setup\u002Ffeatures-geometries-and-memory-layers\u002Fundo-edits-with-edit-buffer-pyqgis\u002F","undoing edits with the edit buffer",".",[135,1594,1596],{"id":1595},"qgis-version-compatibility","QGIS version compatibility",[14,1598,1599,1601,1602,1605,1606,1609,1610,1592],{},[27,1600,29],{}," exists from QGIS 3.0; behaviours 4 to 7 were added during the 3.x series and are present on 3.34 LTR, 3.40 LTR and QGIS 4. ",[27,1603,1604],{},"hausdorffDistance"," and ",[27,1607,1608],{},"native:snappointstogrid"," are available on all current releases. Confirm behaviour numbers on your version with ",[27,1611,1612],{},"processing.algorithmHelp(\"native:snapgeometries\")",[135,1614,1616],{"id":1615},"troubleshooting","Troubleshooting",[140,1618,1619,1625,1633,1639],{},[143,1620,1621,1624],{},[385,1622,1623],{},"Nothing moved."," The tolerance is in the wrong units (the layer is in degrees), or input and reference are in different CRSs.",[143,1626,1627,1630,1631,1592],{},[385,1628,1629],{},"Polygons collapsed or became invalid."," The tolerance is too large relative to feature width; reduce it and run ",[27,1632,779],{},[143,1634,1635,1638],{},[385,1636,1637],{},"Vertex counts exploded."," A behaviour that inserts vertices was used against a very detailed reference; try 2 or 3.",[143,1640,1641,1644],{},[385,1642,1643],{},"Neighbouring polygons still have slivers after self-snapping."," Use behaviour 7 and run a second pass with the same tolerance.",[135,1646,1648],{"id":1647},"conclusion","Conclusion",[14,1650,1651],{},"Snap with an explicit tolerance in metres and a behaviour chosen for the job — nodes for shared boundaries, closest point for features that lie on a reference, end points for networks, anchors for self-snapping — fix geometries afterwards, audit every feature's displacement and area change, and write back only the changes that pass.",[135,1653,1655],{"id":1654},"frequently-asked-questions","Frequently Asked Questions",[14,1657,1658,1661,1662,1664],{},[385,1659,1660],{},"Is snapping the same as the digitizing snapping options?","\nNo. Digitizing snapping guides the cursor while editing; ",[27,1663,29],{}," moves existing geometry in bulk.",[14,1666,1667,1670],{},[385,1668,1669],{},"Can I snap points to lines?","\nYes. Use a point layer as input, a line layer as reference and behaviour 1 or 3 for the closest point.",[14,1672,1673,1676],{},[385,1674,1675],{},"Does snapping preserve attributes?","\nYes. Only geometry changes.",[14,1678,1679,1682],{},[385,1680,1681],{},"How do I choose the tolerance?","\nFrom the data's accuracy and from the size of the gaps you measured — the smallest value that closes them.",[135,1684,1686],{"id":1685},"related","Related",[140,1688,1689,1694,1699,1705,1711],{},[143,1690,1691,1693],{},[21,1692,24],{"href":23}," — the guide this recipe belongs to",[143,1695,1696],{},[21,1697,1698],{"href":774},"Find Gaps and Overlaps Between Polygons in PyQGIS",[143,1700,1701],{},[21,1702,1704],{"href":1703},"\u002Fspatial-data-processing-automation\u002Fdata-quality-and-topology-validation\u002Fcheck-line-network-connectivity-pyqgis\u002F","Check Line Network Connectivity in PyQGIS",[143,1706,1707],{},[21,1708,1710],{"href":1709},"\u002Fqgis-plugin-development\u002Fcustom-map-tools-and-canvas-interaction\u002Fsnap-to-features-with-qgssnappingutils-pyqgis\u002F","Snap to Features with QgsSnappingUtils",[143,1712,1713],{},[21,1714,1716],{"href":1715},"\u002Fspatial-data-processing-automation\u002Fgeometry-operations-and-predicates\u002Ffix-invalid-geometries-pyqgis\u002F","Fix Invalid Geometries in PyQGIS",[1718,1719,1720],"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 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":170,"searchDepth":187,"depth":187,"links":1722},[1723,1724,1725,1726,1727,1728,1729,1730,1731,1732,1733,1734,1735],{"id":137,"depth":187,"text":138},{"id":159,"depth":187,"text":160},{"id":391,"depth":187,"text":392},{"id":656,"depth":187,"text":657},{"id":783,"depth":187,"text":784},{"id":878,"depth":187,"text":879},{"id":1087,"depth":187,"text":1088},{"id":1441,"depth":187,"text":1442},{"id":1595,"depth":187,"text":1596},{"id":1615,"depth":187,"text":1616},{"id":1647,"depth":187,"text":1648},{"id":1654,"depth":187,"text":1655},{"id":1685,"depth":187,"text":1686},"Align vertices and edges between layers with the Snap Geometries algorithm — choosing a tolerance and snapping behaviour, snapping a layer to itself to close slivers and undershoots, snapping to a grid, and measuring what moved so nothing changes silently.","md",{"slug":1739,"type":1740,"breadcrumb":1741,"datePublished":1742,"dateModified":1742},"snap-geometries-to-layer-pyqgis","article","Snap Geometries to a Layer","2026-10-02","\u002Fspatial-data-processing-automation\u002Fdata-quality-and-topology-validation\u002Fsnap-geometries-to-layer-pyqgis",{"title":5,"description":1736},"spatial-data-processing-automation\u002Fdata-quality-and-topology-validation\u002Fsnap-geometries-to-layer-pyqgis\u002Findex","Tg0iUQocOZ8wxBENbpAmY3d0ZW39nNFBPksI9TW7emE",1790966261500]