[{"data":1,"prerenderedAt":2107},["ShallowReactive",2],{"doc:\u002Fspatial-data-processing-automation\u002Fspatial-statistics-and-pattern-analysis\u002Fcreate-voronoi-and-delaunay-pyqgis":3},{"id":4,"title":5,"body":6,"description":2096,"extension":2097,"meta":2098,"navigation":208,"path":2103,"seo":2104,"stem":2105,"__hash__":2106},"docs\u002Fspatial-data-processing-automation\u002Fspatial-statistics-and-pattern-analysis\u002Fcreate-voronoi-and-delaunay-pyqgis\u002Findex.md","Create Voronoi Polygons and Delaunay Triangles in PyQGIS",{"type":7,"value":8,"toc":2081},"minimark",[9,13,17,26,141,146,159,163,166,357,372,376,379,474,698,711,715,718,828,838,842,845,1028,1038,1042,1045,1332,1346,1350,1353,1424,1644,1653,1657,1665,1933,1938,1942,1968,1972,1998,2002,2008,2012,2018,2028,2038,2044,2048,2077],[10,11,5],"h1",{"id":12},"create-voronoi-polygons-and-delaunay-triangles-in-pyqgis",[14,15,16],"p",{},"Given a set of points, two structures describe how they share space. Voronoi polygons — also called Thiessen polygons — give every point the region of the plane closer to it than to any other point. The Delaunay triangulation connects points whose Voronoi cells touch, forming triangles that are as close to equilateral as the points allow. They are two views of the same geometry, and between them they answer a surprising range of practical questions: which weather station's reading applies to this field, which school is nearest each address, which points are neighbours of which.",[14,18,19,20,25],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fspatial-data-processing-automation\u002Fspatial-statistics-and-pattern-analysis\u002F","Spatial Statistics & Pattern Analysis",". It builds both structures with Processing, clips Voronoi cells to a meaningful area, uses them for proximity allocation, and turns the triangulation into a list of neighbours.",[14,27,28],{},[29,30,35,39,43,50,59,69,76,79,82,85,88,95,97,100,102,105,111,114,118,121,124,127,130,133,135,137],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"xmlns":34},"0 0 760 280","img","Voronoi cells around five points next to the Delaunay triangulation of the same points, showing that cells sharing a boundary correspond to connected points","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[36,37,38],"title",{},"Voronoi cells and Delaunay triangles are duals",[40,41,42],"desc",{},"Left: five points with their Voronoi cells; every location inside a cell is closer to that cell's point than to any other. Right: the same points connected by Delaunay triangles; two points are joined by an edge exactly when their Voronoi cells share a boundary. Each Voronoi edge is the perpendicular bisector of the matching Delaunay edge.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","280","#f6f3ea",[51,52,58],"text",{"x":53,"y":54,"style":55,"fill":56,"textAnchor":57},"380","28","text-anchor:middle;font-size:14px;font-family:sans-serif;font-weight:bold","#17211d","middle","Same points, two structures",[44,60],{"x":61,"y":62,"width":63,"height":64,"rx":65,"fill":66,"stroke":67,"style":68},"24","48","340","200","8","#fffdf7","#59645f","stroke-width:1.5",[70,71],"polyline",{"points":72,"fill":73,"stroke":74,"style":75},"120,48 150,130 110,248","none","#0f766e","stroke-width:2",[70,77],{"points":78,"fill":73,"stroke":74,"style":75},"150,130 250,120 290,48",[70,80],{"points":81,"fill":73,"stroke":74,"style":75},"250,120 260,248",[70,83],{"points":84,"fill":73,"stroke":74,"style":75},"150,130 24,150",[70,86],{"points":87,"fill":73,"stroke":74,"style":75},"250,120 364,150",[89,90],"circle",{"cx":91,"cy":92,"r":93,"fill":56,"stroke":56,"style":94},"80","90","5","stroke-width:1",[89,96],{"cx":64,"cy":91,"r":93,"fill":56,"stroke":56,"style":94},[89,98],{"cx":99,"cy":92,"r":93,"fill":56,"stroke":56,"style":94},"320",[89,101],{"cx":91,"cy":64,"r":93,"fill":56,"stroke":56,"style":94},[89,103],{"cx":64,"cy":104,"r":93,"fill":56,"stroke":56,"style":94},"190",[51,106,110],{"x":107,"y":108,"style":109,"fill":74,"textAnchor":57},"194","270","text-anchor:middle;font-size:10.5px;font-family:sans-serif","Voronoi: region nearest each point",[44,112],{"x":113,"y":62,"width":63,"height":64,"rx":65,"fill":66,"stroke":67,"style":68},"396",[70,115],{"points":116,"fill":73,"stroke":117,"style":75},"452,90 572,80 692,90 572,190 452,200 452,90","#b45309",[70,119],{"points":120,"fill":73,"stroke":117,"style":75},"452,90 572,190",[70,122],{"points":123,"fill":73,"stroke":117,"style":75},"572,80 572,190",[89,125],{"cx":126,"cy":92,"r":93,"fill":56,"stroke":56,"style":94},"452",[89,128],{"cx":129,"cy":91,"r":93,"fill":56,"stroke":56,"style":94},"572",[89,131],{"cx":132,"cy":92,"r":93,"fill":56,"stroke":56,"style":94},"692",[89,134],{"cx":126,"cy":64,"r":93,"fill":56,"stroke":56,"style":94},[89,136],{"cx":129,"cy":104,"r":93,"fill":56,"stroke":56,"style":94},[51,138,140],{"x":139,"y":108,"style":109,"fill":117,"textAnchor":57},"566","Delaunay: neighbours joined",[142,143,145],"h2",{"id":144},"prerequisites","Prerequisites",[147,148,149,153,156],"ul",{},[150,151,152],"li",{},"QGIS 3.34 LTR or newer, or the QGIS 4 series.",[150,154,155],{},"A point layer in a projected CRS. Voronoi cells drawn in degrees are distorted, especially at high latitudes.",[150,157,158],{},"No duplicate points. Two points at the same location have no meaningful boundary between them; remove duplicates first.",[142,160,162],{"id":161},"build-voronoi-polygons","Build Voronoi polygons",[14,164,165],{},"The Voronoi algorithm takes a point layer and a buffer percentage that controls how far the outer cells extend beyond the points' extent. Attributes of each point are copied to its cell.",[167,168,173],"pre",{"className":169,"code":170,"language":171,"meta":172,"style":172},"language-python shiki shiki-themes github-dark","import processing\nfrom qgis.core import QgsProject\n\nstations = QgsProject.instance().mapLayersByName(\"rain_gauges\")[0]\ncells = processing.run(\"native:voronoipolygons\", {\n    \"INPUT\": stations,\n    \"BUFFER\": 10,                 # percent of extent added around the points\n    \"TOLERANCE\": 0,\n    \"COPY_ATTRIBUTES\": True,\n    \"OUTPUT\": \"memory:gauge_cells\",\n})[\"OUTPUT\"]\nprint(cells.featureCount(), \"cells for\", stations.featureCount(), \"gauges\")\nQgsProject.instance().addMapLayer(cells)\n","python","",[174,175,176,189,203,210,235,252,261,280,293,306,319,330,351],"code",{"__ignoreMap":172},[177,178,181,185],"span",{"class":179,"line":180},"line",1,[177,182,184],{"class":183},"snl16","import",[177,186,188],{"class":187},"s95oV"," processing\n",[177,190,192,195,198,200],{"class":179,"line":191},2,[177,193,194],{"class":183},"from",[177,196,197],{"class":187}," qgis.core ",[177,199,184],{"class":183},[177,201,202],{"class":187}," QgsProject\n",[177,204,206],{"class":179,"line":205},3,[177,207,209],{"emptyLinePlaceholder":208},true,"\n",[177,211,213,216,219,222,226,229,232],{"class":179,"line":212},4,[177,214,215],{"class":187},"stations ",[177,217,218],{"class":183},"=",[177,220,221],{"class":187}," QgsProject.instance().mapLayersByName(",[177,223,225],{"class":224},"sU2Wk","\"rain_gauges\"",[177,227,228],{"class":187},")[",[177,230,46],{"class":231},"sDLfK",[177,233,234],{"class":187},"]\n",[177,236,238,241,243,246,249],{"class":179,"line":237},5,[177,239,240],{"class":187},"cells ",[177,242,218],{"class":183},[177,244,245],{"class":187}," processing.run(",[177,247,248],{"class":224},"\"native:voronoipolygons\"",[177,250,251],{"class":187},", {\n",[177,253,255,258],{"class":179,"line":254},6,[177,256,257],{"class":224},"    \"INPUT\"",[177,259,260],{"class":187},": stations,\n",[177,262,264,267,270,273,276],{"class":179,"line":263},7,[177,265,266],{"class":224},"    \"BUFFER\"",[177,268,269],{"class":187},": ",[177,271,272],{"class":231},"10",[177,274,275],{"class":187},",                 ",[177,277,279],{"class":278},"sjoCn","# percent of extent added around the points\n",[177,281,283,286,288,290],{"class":179,"line":282},8,[177,284,285],{"class":224},"    \"TOLERANCE\"",[177,287,269],{"class":187},[177,289,46],{"class":231},[177,291,292],{"class":187},",\n",[177,294,296,299,301,304],{"class":179,"line":295},9,[177,297,298],{"class":224},"    \"COPY_ATTRIBUTES\"",[177,300,269],{"class":187},[177,302,303],{"class":231},"True",[177,305,292],{"class":187},[177,307,309,312,314,317],{"class":179,"line":308},10,[177,310,311],{"class":224},"    \"OUTPUT\"",[177,313,269],{"class":187},[177,315,316],{"class":224},"\"memory:gauge_cells\"",[177,318,292],{"class":187},[177,320,322,325,328],{"class":179,"line":321},11,[177,323,324],{"class":187},"})[",[177,326,327],{"class":224},"\"OUTPUT\"",[177,329,234],{"class":187},[177,331,333,336,339,342,345,348],{"class":179,"line":332},12,[177,334,335],{"class":231},"print",[177,337,338],{"class":187},"(cells.featureCount(), ",[177,340,341],{"class":224},"\"cells for\"",[177,343,344],{"class":187},", stations.featureCount(), ",[177,346,347],{"class":224},"\"gauges\"",[177,349,350],{"class":187},")\n",[177,352,354],{"class":179,"line":353},13,[177,355,356],{"class":187},"QgsProject.instance().addMapLayer(cells)\n",[14,358,359,363,364,367,368,371],{},[360,361,362],"strong",{},"Breakdown:"," Each output polygon carries the attributes of the point it surrounds, so a cell can be styled or joined by the gauge's id or reading directly. Cells along the outside of the point set are unbounded in theory; the buffer clips them to a rectangle slightly larger than the points' extent. A count mismatch between cells and points usually means duplicate points. ",[174,365,366],{},"TOLERANCE"," snaps nearly coincident points before building and can be left at zero for clean data; on QGIS releases before the algorithm moved to the native provider, the id is ",[174,369,370],{},"qgis:voronoipolygons"," and the tolerance parameter is absent.",[142,373,375],{"id":374},"clip-cells-to-a-meaningful-area","Clip cells to a meaningful area",[14,377,378],{},"The rectangle the buffer produces is arbitrary. For analysis, cells should stop at the boundary of the area they describe — a catchment, a country, a coastline.",[14,380,381],{},[29,382,385,388,391,394,409,412,417,423,427,431,437,440,445,449,453,459,464,468,471],{"viewBox":383,"role":32,"ariaLabel":384,"xmlns":34},"0 0 760 240","Voronoi cells filling an arbitrary rectangle, then clipped to the study area boundary with areas recalculated",[36,386,387],{},"From a bounding rectangle to a real boundary",[40,389,390],{},"Raw Voronoi output fills a rectangle around the points, so outer cells extend into the sea and beyond the region. Clipping with the study area polygon trims every cell to the region, and cell areas are recalculated afterwards so that weights such as Thiessen areas are correct.",[44,392],{"x":46,"y":46,"width":47,"height":393,"fill":49},"240",[395,396,397],"defs",{},[398,399,404],"marker",{"id":400,"viewBox":401,"refX":65,"refY":93,"markerWidth":402,"markerHeight":402,"orient":403},"vorClipArrow","0 0 10 10","7","auto-start-reverse",[405,406],"path",{"d":407,"fill":408},"M0 0 L10 5 L0 10 z","#2f3b35",[51,410,411],{"x":53,"y":54,"style":55,"fill":56,"textAnchor":57},"Clip, then measure",[44,413],{"x":61,"y":414,"width":415,"height":416,"rx":65,"fill":66,"stroke":67,"style":75},"60","222","130",[51,418,422],{"x":419,"y":420,"style":421,"fill":56,"textAnchor":57},"135","102.78","text-anchor:middle;font-size:11.5px;font-family:sans-serif;font-weight:bold","raw cells",[51,424,426],{"x":419,"y":425,"style":109,"fill":67,"textAnchor":57},"128.78","fill a rectangle",[51,428,430],{"x":419,"y":429,"style":109,"fill":67,"textAnchor":57},"154.78","extend into the sea",[179,432],{"x1":433,"y1":434,"x2":435,"y2":434,"stroke":408,"style":436},"246","125","266","stroke-width:1.8;marker-end:url(#vorClipArrow)",[44,438],{"x":108,"y":414,"width":415,"height":416,"rx":65,"fill":439,"stroke":74,"style":75},"#eef7f4",[51,441,444],{"x":442,"y":443,"style":421,"fill":74,"textAnchor":57},"381","115.78","native:clip",[51,446,448],{"x":442,"y":447,"style":109,"fill":67,"textAnchor":57},"141.78","overlay: study area",[179,450],{"x1":451,"y1":434,"x2":452,"y2":434,"stroke":408,"style":436},"492","512",[44,454],{"x":455,"y":414,"width":456,"height":416,"rx":65,"fill":457,"stroke":458,"style":75},"516","220","#e8efe6","#15803d",[51,460,463],{"x":461,"y":420,"style":421,"fill":462,"textAnchor":57},"626","#166534","clipped cells",[51,465,467],{"x":461,"y":425,"style":466,"fill":408,"textAnchor":57},"text-anchor:middle;font-size:10.0px;font-family:monospace","$area recomputed",[51,469,470],{"x":461,"y":429,"style":109,"fill":67,"textAnchor":57},"honest weights",[51,472,473],{"x":53,"y":415,"style":109,"fill":67,"textAnchor":57},"always recompute areas after clipping",[167,475,477],{"className":169,"code":476,"language":171,"meta":172,"style":172},"basin = QgsProject.instance().mapLayersByName(\"river_basin\")[0]\nclipped = processing.run(\"native:clip\", {\n    \"INPUT\": cells, \"OVERLAY\": basin, \"OUTPUT\": \"memory:gauge_cells_basin\"})[\"OUTPUT\"]\n\nareas = processing.run(\"native:fieldcalculator\", {\n    \"INPUT\": clipped, \"FIELD_NAME\": \"cell_km2\", \"FIELD_TYPE\": 0,\n    \"FIELD_LENGTH\": 12, \"FIELD_PRECISION\": 3,\n    \"FORMULA\": \"$area \u002F 1e6\", \"OUTPUT\": \"memory:gauge_cells_final\"})[\"OUTPUT\"]\n\ntotal = sum(f[\"cell_km2\"] for f in areas.getFeatures())\nprint(f\"basin area covered: {total:.1f} km²\")\nQgsProject.instance().addMapLayer(areas)\n",[174,478,479,497,511,537,541,555,582,604,629,633,663,693],{"__ignoreMap":172},[177,480,481,484,486,488,491,493,495],{"class":179,"line":180},[177,482,483],{"class":187},"basin ",[177,485,218],{"class":183},[177,487,221],{"class":187},[177,489,490],{"class":224},"\"river_basin\"",[177,492,228],{"class":187},[177,494,46],{"class":231},[177,496,234],{"class":187},[177,498,499,502,504,506,509],{"class":179,"line":191},[177,500,501],{"class":187},"clipped ",[177,503,218],{"class":183},[177,505,245],{"class":187},[177,507,508],{"class":224},"\"native:clip\"",[177,510,251],{"class":187},[177,512,513,515,518,521,524,526,528,531,533,535],{"class":179,"line":205},[177,514,257],{"class":224},[177,516,517],{"class":187},": cells, ",[177,519,520],{"class":224},"\"OVERLAY\"",[177,522,523],{"class":187},": basin, ",[177,525,327],{"class":224},[177,527,269],{"class":187},[177,529,530],{"class":224},"\"memory:gauge_cells_basin\"",[177,532,324],{"class":187},[177,534,327],{"class":224},[177,536,234],{"class":187},[177,538,539],{"class":179,"line":212},[177,540,209],{"emptyLinePlaceholder":208},[177,542,543,546,548,550,553],{"class":179,"line":237},[177,544,545],{"class":187},"areas ",[177,547,218],{"class":183},[177,549,245],{"class":187},[177,551,552],{"class":224},"\"native:fieldcalculator\"",[177,554,251],{"class":187},[177,556,557,559,562,565,567,570,573,576,578,580],{"class":179,"line":254},[177,558,257],{"class":224},[177,560,561],{"class":187},": clipped, ",[177,563,564],{"class":224},"\"FIELD_NAME\"",[177,566,269],{"class":187},[177,568,569],{"class":224},"\"cell_km2\"",[177,571,572],{"class":187},", ",[177,574,575],{"class":224},"\"FIELD_TYPE\"",[177,577,269],{"class":187},[177,579,46],{"class":231},[177,581,292],{"class":187},[177,583,584,587,589,592,594,597,599,602],{"class":179,"line":263},[177,585,586],{"class":224},"    \"FIELD_LENGTH\"",[177,588,269],{"class":187},[177,590,591],{"class":231},"12",[177,593,572],{"class":187},[177,595,596],{"class":224},"\"FIELD_PRECISION\"",[177,598,269],{"class":187},[177,600,601],{"class":231},"3",[177,603,292],{"class":187},[177,605,606,609,611,614,616,618,620,623,625,627],{"class":179,"line":282},[177,607,608],{"class":224},"    \"FORMULA\"",[177,610,269],{"class":187},[177,612,613],{"class":224},"\"$area \u002F 1e6\"",[177,615,572],{"class":187},[177,617,327],{"class":224},[177,619,269],{"class":187},[177,621,622],{"class":224},"\"memory:gauge_cells_final\"",[177,624,324],{"class":187},[177,626,327],{"class":224},[177,628,234],{"class":187},[177,630,631],{"class":179,"line":295},[177,632,209],{"emptyLinePlaceholder":208},[177,634,635,638,640,643,646,648,651,654,657,660],{"class":179,"line":308},[177,636,637],{"class":187},"total ",[177,639,218],{"class":183},[177,641,642],{"class":231}," sum",[177,644,645],{"class":187},"(f[",[177,647,569],{"class":224},[177,649,650],{"class":187},"] ",[177,652,653],{"class":183},"for",[177,655,656],{"class":187}," f ",[177,658,659],{"class":183},"in",[177,661,662],{"class":187}," areas.getFeatures())\n",[177,664,665,667,670,673,676,679,682,685,688,691],{"class":179,"line":321},[177,666,335],{"class":231},[177,668,669],{"class":187},"(",[177,671,672],{"class":183},"f",[177,674,675],{"class":224},"\"basin area covered: ",[177,677,678],{"class":231},"{",[177,680,681],{"class":187},"total",[177,683,684],{"class":183},":.1f",[177,686,687],{"class":231},"}",[177,689,690],{"class":224}," km²\"",[177,692,350],{"class":187},[177,694,695],{"class":179,"line":332},[177,696,697],{"class":187},"QgsProject.instance().addMapLayer(areas)\n",[14,699,700,702,703,706,707,710],{},[360,701,362],{}," Clipping keeps attributes, so the cells remain linked to their gauges. Area must be recalculated after clipping, because a cell that extended into the sea is now smaller; ",[174,704,705],{},"$area"," uses the project's ellipsoid settings, while ",[174,708,709],{},"area($geometry)"," gives planar area in layer units. The sum of cell areas should equal the basin area — a quick check that no cell was lost. A gauge outside the basin may still own a cell that reaches inside it; that is correct, as the nearest gauge to those locations is outside.",[142,712,714],{"id":713},"weight-values-by-thiessen-area","Weight values by Thiessen area",[14,716,717],{},"The classic use of Voronoi cells is the Thiessen method for areal averages: each gauge's rainfall is weighted by the area of its cell within the basin, giving a basin-average rainfall that does not over-weight clusters of gauges.",[167,719,721],{"className":169,"code":720,"language":171,"meta":172,"style":172},"weighted = sum(f[\"rain_mm\"] * f[\"cell_km2\"] for f in areas.getFeatures())\nsimple = sum(f[\"rain_mm\"] for f in areas.getFeatures()) \u002F areas.featureCount()\nprint(f\"Thiessen mean: {weighted \u002F total:.1f} mm | simple mean: {simple:.1f} mm\")\n",[174,722,723,757,787],{"__ignoreMap":172},[177,724,725,728,730,732,734,737,739,742,745,747,749,751,753,755],{"class":179,"line":180},[177,726,727],{"class":187},"weighted ",[177,729,218],{"class":183},[177,731,642],{"class":231},[177,733,645],{"class":187},[177,735,736],{"class":224},"\"rain_mm\"",[177,738,650],{"class":187},[177,740,741],{"class":183},"*",[177,743,744],{"class":187}," f[",[177,746,569],{"class":224},[177,748,650],{"class":187},[177,750,653],{"class":183},[177,752,656],{"class":187},[177,754,659],{"class":183},[177,756,662],{"class":187},[177,758,759,762,764,766,768,770,772,774,776,778,781,784],{"class":179,"line":191},[177,760,761],{"class":187},"simple ",[177,763,218],{"class":183},[177,765,642],{"class":231},[177,767,645],{"class":187},[177,769,736],{"class":224},[177,771,650],{"class":187},[177,773,653],{"class":183},[177,775,656],{"class":187},[177,777,659],{"class":183},[177,779,780],{"class":187}," areas.getFeatures()) ",[177,782,783],{"class":183},"\u002F",[177,785,786],{"class":187}," areas.featureCount()\n",[177,788,789,791,793,795,798,800,802,804,807,809,811,814,816,819,821,823,826],{"class":179,"line":205},[177,790,335],{"class":231},[177,792,669],{"class":187},[177,794,672],{"class":183},[177,796,797],{"class":224},"\"Thiessen mean: ",[177,799,678],{"class":231},[177,801,727],{"class":187},[177,803,783],{"class":183},[177,805,806],{"class":187}," total",[177,808,684],{"class":183},[177,810,687],{"class":231},[177,812,813],{"class":224}," mm | simple mean: ",[177,815,678],{"class":231},[177,817,818],{"class":187},"simple",[177,820,684],{"class":183},[177,822,687],{"class":231},[177,824,825],{"class":224}," mm\"",[177,827,350],{"class":187},[14,829,830,832,833,837],{},[360,831,362],{}," Where gauges are clustered — several in a city, one on a mountain — a simple mean lets the cluster dominate. The Thiessen weights reflect how much of the basin each gauge represents. The difference between the two means is itself informative: a large gap shows that the network is unevenly spread and that the simple mean would mislead. Interpolation methods such as ",[21,834,836],{"href":835},"\u002Fspatial-data-processing-automation\u002Fterrain-and-interpolation-analysis\u002Fidw-interpolation-from-points-pyqgis\u002F","IDW"," give a continuous surface instead of constant cells.",[142,839,841],{"id":840},"allocate-locations-to-their-nearest-site","Allocate locations to their nearest site",[14,843,844],{},"Voronoi cells are a fast way to allocate many locations to the nearest of a few sites by straight-line distance: build cells around the sites once, then a point-in-polygon join does the rest.",[167,846,848],{"className":169,"code":847,"language":171,"meta":172,"style":172},"schools = QgsProject.instance().mapLayersByName(\"primary_schools\")[0]\naddresses = QgsProject.instance().mapLayersByName(\"addresses\")[0]\n\nschool_cells = processing.run(\"native:voronoipolygons\", {\n    \"INPUT\": schools, \"BUFFER\": 20, \"COPY_ATTRIBUTES\": True,\n    \"OUTPUT\": \"memory:\"})[\"OUTPUT\"]\nallocated = processing.run(\"native:joinattributesbylocation\", {\n    \"INPUT\": addresses, \"JOIN\": school_cells, \"PREDICATE\": [5],   # within\n    \"JOIN_FIELDS\": [\"school_name\"], \"METHOD\": 1,\n    \"OUTPUT\": \"memory:addresses_nearest_school\"})[\"OUTPUT\"]\nQgsProject.instance().addMapLayer(allocated)\n",[174,849,850,868,886,890,903,929,944,958,985,1008,1023],{"__ignoreMap":172},[177,851,852,855,857,859,862,864,866],{"class":179,"line":180},[177,853,854],{"class":187},"schools ",[177,856,218],{"class":183},[177,858,221],{"class":187},[177,860,861],{"class":224},"\"primary_schools\"",[177,863,228],{"class":187},[177,865,46],{"class":231},[177,867,234],{"class":187},[177,869,870,873,875,877,880,882,884],{"class":179,"line":191},[177,871,872],{"class":187},"addresses ",[177,874,218],{"class":183},[177,876,221],{"class":187},[177,878,879],{"class":224},"\"addresses\"",[177,881,228],{"class":187},[177,883,46],{"class":231},[177,885,234],{"class":187},[177,887,888],{"class":179,"line":205},[177,889,209],{"emptyLinePlaceholder":208},[177,891,892,895,897,899,901],{"class":179,"line":212},[177,893,894],{"class":187},"school_cells ",[177,896,218],{"class":183},[177,898,245],{"class":187},[177,900,248],{"class":224},[177,902,251],{"class":187},[177,904,905,907,910,913,915,918,920,923,925,927],{"class":179,"line":237},[177,906,257],{"class":224},[177,908,909],{"class":187},": schools, ",[177,911,912],{"class":224},"\"BUFFER\"",[177,914,269],{"class":187},[177,916,917],{"class":231},"20",[177,919,572],{"class":187},[177,921,922],{"class":224},"\"COPY_ATTRIBUTES\"",[177,924,269],{"class":187},[177,926,303],{"class":231},[177,928,292],{"class":187},[177,930,931,933,935,938,940,942],{"class":179,"line":254},[177,932,311],{"class":224},[177,934,269],{"class":187},[177,936,937],{"class":224},"\"memory:\"",[177,939,324],{"class":187},[177,941,327],{"class":224},[177,943,234],{"class":187},[177,945,946,949,951,953,956],{"class":179,"line":263},[177,947,948],{"class":187},"allocated ",[177,950,218],{"class":183},[177,952,245],{"class":187},[177,954,955],{"class":224},"\"native:joinattributesbylocation\"",[177,957,251],{"class":187},[177,959,960,962,965,968,971,974,977,979,982],{"class":179,"line":282},[177,961,257],{"class":224},[177,963,964],{"class":187},": addresses, ",[177,966,967],{"class":224},"\"JOIN\"",[177,969,970],{"class":187},": school_cells, ",[177,972,973],{"class":224},"\"PREDICATE\"",[177,975,976],{"class":187},": [",[177,978,93],{"class":231},[177,980,981],{"class":187},"],   ",[177,983,984],{"class":278},"# within\n",[177,986,987,990,992,995,998,1001,1003,1006],{"class":179,"line":295},[177,988,989],{"class":224},"    \"JOIN_FIELDS\"",[177,991,976],{"class":187},[177,993,994],{"class":224},"\"school_name\"",[177,996,997],{"class":187},"], ",[177,999,1000],{"class":224},"\"METHOD\"",[177,1002,269],{"class":187},[177,1004,1005],{"class":231},"1",[177,1007,292],{"class":187},[177,1009,1010,1012,1014,1017,1019,1021],{"class":179,"line":308},[177,1011,311],{"class":224},[177,1013,269],{"class":187},[177,1015,1016],{"class":224},"\"memory:addresses_nearest_school\"",[177,1018,324],{"class":187},[177,1020,327],{"class":224},[177,1022,234],{"class":187},[177,1024,1025],{"class":179,"line":321},[177,1026,1027],{"class":187},"QgsProject.instance().addMapLayer(allocated)\n",[14,1029,1030,1032,1033,1037],{},[360,1031,362],{}," For thousands of addresses and dozens of sites, building cells once and joining is much faster than a nearest-neighbour search per address, and the cells themselves are a useful map of the allocation. The limitation is the distance measure: cells use straight-line distance, so a river without a bridge or a motorway without a crossing is ignored. For allocation by travel time, use ",[21,1034,1036],{"href":1035},"\u002Fspatial-data-processing-automation\u002Fnetwork-analysis-and-routing\u002Fcalculate-service-areas-pyqgis\u002F","service areas"," or an origin–destination matrix instead.",[142,1039,1041],{"id":1040},"measure-how-catchments-cross-administrative-lines","Measure how catchments cross administrative lines",[14,1043,1044],{},"Once sites have cells, overlaying them with administrative units shows how a site's catchment is split — useful for funding formulas, reporting by district, or spotting a school whose nearest-pupils area straddles a council boundary.",[167,1046,1048],{"className":169,"code":1047,"language":171,"meta":172,"style":172},"districts = QgsProject.instance().mapLayersByName(\"districts\")[0]\npieces = processing.run(\"native:intersection\", {\n    \"INPUT\": school_cells, \"OVERLAY\": districts,\n    \"INPUT_FIELDS\": [\"school_name\"], \"OVERLAY_FIELDS\": [\"district\"],\n    \"OUTPUT\": \"memory:\"})[\"OUTPUT\"]\n\nshares = defaultdict(dict)\nfor f in pieces.getFeatures():\n    shares[f[\"school_name\"]][f[\"district\"]] = f.geometry().area()\nfor school, parts in shares.items():\n    total = sum(parts.values())\n    if len(parts) > 1:\n        split = \", \".join(f\"{d} {a \u002F total:.0%}\" for d, a in sorted(parts.items(), key=lambda kv: -kv[1]))\n        print(f\"{school}: {split}\")\n",[174,1049,1050,1068,1082,1093,1115,1129,1133,1148,1159,1179,1191,1203,1223,1300],{"__ignoreMap":172},[177,1051,1052,1055,1057,1059,1062,1064,1066],{"class":179,"line":180},[177,1053,1054],{"class":187},"districts ",[177,1056,218],{"class":183},[177,1058,221],{"class":187},[177,1060,1061],{"class":224},"\"districts\"",[177,1063,228],{"class":187},[177,1065,46],{"class":231},[177,1067,234],{"class":187},[177,1069,1070,1073,1075,1077,1080],{"class":179,"line":191},[177,1071,1072],{"class":187},"pieces ",[177,1074,218],{"class":183},[177,1076,245],{"class":187},[177,1078,1079],{"class":224},"\"native:intersection\"",[177,1081,251],{"class":187},[177,1083,1084,1086,1088,1090],{"class":179,"line":205},[177,1085,257],{"class":224},[177,1087,970],{"class":187},[177,1089,520],{"class":224},[177,1091,1092],{"class":187},": districts,\n",[177,1094,1095,1098,1100,1102,1104,1107,1109,1112],{"class":179,"line":212},[177,1096,1097],{"class":224},"    \"INPUT_FIELDS\"",[177,1099,976],{"class":187},[177,1101,994],{"class":224},[177,1103,997],{"class":187},[177,1105,1106],{"class":224},"\"OVERLAY_FIELDS\"",[177,1108,976],{"class":187},[177,1110,1111],{"class":224},"\"district\"",[177,1113,1114],{"class":187},"],\n",[177,1116,1117,1119,1121,1123,1125,1127],{"class":179,"line":237},[177,1118,311],{"class":224},[177,1120,269],{"class":187},[177,1122,937],{"class":224},[177,1124,324],{"class":187},[177,1126,327],{"class":224},[177,1128,234],{"class":187},[177,1130,1131],{"class":179,"line":254},[177,1132,209],{"emptyLinePlaceholder":208},[177,1134,1135,1138,1140,1143,1146],{"class":179,"line":263},[177,1136,1137],{"class":187},"shares ",[177,1139,218],{"class":183},[177,1141,1142],{"class":187}," defaultdict(",[177,1144,1145],{"class":231},"dict",[177,1147,350],{"class":187},[177,1149,1150,1152,1154,1156],{"class":179,"line":282},[177,1151,653],{"class":183},[177,1153,656],{"class":187},[177,1155,659],{"class":183},[177,1157,1158],{"class":187}," pieces.getFeatures():\n",[177,1160,1161,1164,1166,1169,1171,1174,1176],{"class":179,"line":295},[177,1162,1163],{"class":187},"    shares[f[",[177,1165,994],{"class":224},[177,1167,1168],{"class":187},"]][f[",[177,1170,1111],{"class":224},[177,1172,1173],{"class":187},"]] ",[177,1175,218],{"class":183},[177,1177,1178],{"class":187}," f.geometry().area()\n",[177,1180,1181,1183,1186,1188],{"class":179,"line":308},[177,1182,653],{"class":183},[177,1184,1185],{"class":187}," school, parts ",[177,1187,659],{"class":183},[177,1189,1190],{"class":187}," shares.items():\n",[177,1192,1193,1196,1198,1200],{"class":179,"line":321},[177,1194,1195],{"class":187},"    total ",[177,1197,218],{"class":183},[177,1199,642],{"class":231},[177,1201,1202],{"class":187},"(parts.values())\n",[177,1204,1205,1208,1211,1214,1217,1220],{"class":179,"line":332},[177,1206,1207],{"class":183},"    if",[177,1209,1210],{"class":231}," len",[177,1212,1213],{"class":187},"(parts) ",[177,1215,1216],{"class":183},">",[177,1218,1219],{"class":231}," 1",[177,1221,1222],{"class":187},":\n",[177,1224,1225,1228,1230,1233,1236,1238,1241,1243,1246,1248,1251,1254,1256,1258,1261,1263,1265,1268,1271,1273,1276,1279,1283,1286,1289,1292,1295,1297],{"class":179,"line":353},[177,1226,1227],{"class":187},"        split ",[177,1229,218],{"class":183},[177,1231,1232],{"class":224}," \", \"",[177,1234,1235],{"class":187},".join(",[177,1237,672],{"class":183},[177,1239,1240],{"class":224},"\"",[177,1242,678],{"class":231},[177,1244,1245],{"class":187},"d",[177,1247,687],{"class":231},[177,1249,1250],{"class":231}," {",[177,1252,1253],{"class":187},"a ",[177,1255,783],{"class":183},[177,1257,806],{"class":187},[177,1259,1260],{"class":183},":.0%",[177,1262,687],{"class":231},[177,1264,1240],{"class":224},[177,1266,1267],{"class":183}," for",[177,1269,1270],{"class":187}," d, a ",[177,1272,659],{"class":183},[177,1274,1275],{"class":231}," sorted",[177,1277,1278],{"class":187},"(parts.items(), ",[177,1280,1282],{"class":1281},"s9osk","key",[177,1284,1285],{"class":183},"=lambda",[177,1287,1288],{"class":187}," kv: ",[177,1290,1291],{"class":183},"-",[177,1293,1294],{"class":187},"kv[",[177,1296,1005],{"class":231},[177,1298,1299],{"class":187},"]))\n",[177,1301,1303,1306,1308,1310,1312,1314,1317,1319,1321,1323,1326,1328,1330],{"class":179,"line":1302},14,[177,1304,1305],{"class":231},"        print",[177,1307,669],{"class":187},[177,1309,672],{"class":183},[177,1311,1240],{"class":224},[177,1313,678],{"class":231},[177,1315,1316],{"class":187},"school",[177,1318,687],{"class":231},[177,1320,269],{"class":224},[177,1322,678],{"class":231},[177,1324,1325],{"class":187},"split",[177,1327,687],{"class":231},[177,1329,1240],{"class":224},[177,1331,350],{"class":187},[14,1333,1334,1336,1337,1341,1342,1345],{},[360,1335,362],{}," The intersection splits each cell along district boundaries and keeps one attribute from each side, so every piece knows its school and its district. Summing piece areas per school gives the share of the catchment in each district; printing only schools with more than one district focuses on the cross-boundary cases. Area shares are a proxy — weighting by population, by intersecting with an address layer instead of measuring area, gives the share of ",[1338,1339,1340],"em",{},"people"," rather than land, which is usually what matters. Add ",[174,1343,1344],{},"from collections import defaultdict"," if you run this section on its own.",[142,1347,1349],{"id":1348},"build-a-delaunay-triangulation","Build a Delaunay triangulation",[14,1351,1352],{},"The triangulation algorithm connects the points into triangles. Its output is a polygon layer of triangles; the edges of those triangles are the neighbour relationships.",[14,1354,1355],{},[29,1356,1359,1362,1365,1367,1370,1375,1379,1383,1387,1391,1396,1399,1402,1405,1408,1411,1415,1418,1421],{"viewBox":1357,"role":32,"ariaLabel":1358,"xmlns":34},"0 0 760 222","Three uses of a Delaunay triangulation: neighbour lists from edges, interpolation surfaces from triangles, and long edges revealing gaps",[36,1360,1361],{},"What a triangulation is good for",[40,1363,1364],{},"Three uses of a Delaunay triangulation. Neighbours: each edge connects two natural neighbours, giving a neighbour list for spatial statistics. Surfaces: triangles with point values at their corners form a TIN for linear interpolation. Gaps: very long edges reveal empty areas between groups of points.",[44,1366],{"x":46,"y":46,"width":47,"height":415,"fill":49},[51,1368,1369],{"x":53,"y":54,"style":55,"fill":56,"textAnchor":57},"Triangles, edges and their uses",[44,1371],{"x":61,"y":1372,"width":415,"height":1373,"rx":65,"fill":1374,"stroke":117,"style":75},"56","150","#fdf2e2",[51,1376,1378],{"x":419,"y":1377,"style":421,"fill":117,"textAnchor":57},"95.78","neighbours",[51,1380,1382],{"x":419,"y":1381,"style":109,"fill":408,"textAnchor":57},"121.78","edges link",[51,1384,1386],{"x":419,"y":1385,"style":109,"fill":408,"textAnchor":57},"147.78","natural neighbours",[51,1388,1390],{"x":419,"y":1389,"style":109,"fill":67,"textAnchor":57},"173.78","weights for statistics",[44,1392],{"x":1393,"y":1372,"width":415,"height":1373,"rx":65,"fill":1394,"stroke":1395,"style":75},"269","#eff3ff","#2563eb",[51,1397,1398],{"x":53,"y":1377,"style":421,"fill":1395,"textAnchor":57},"surfaces",[51,1400,1401],{"x":53,"y":1381,"style":109,"fill":408,"textAnchor":57},"values at corners",[51,1403,1404],{"x":53,"y":1385,"style":109,"fill":408,"textAnchor":57},"linear inside",[51,1406,1407],{"x":53,"y":1389,"style":109,"fill":67,"textAnchor":57},"TIN interpolation",[44,1409],{"x":1410,"y":1372,"width":415,"height":1373,"rx":65,"fill":439,"stroke":74,"style":75},"514",[51,1412,1414],{"x":1413,"y":1377,"style":421,"fill":74,"textAnchor":57},"625","gaps",[51,1416,1417],{"x":1413,"y":1381,"style":109,"fill":408,"textAnchor":57},"long edges",[51,1419,1420],{"x":1413,"y":1385,"style":109,"fill":408,"textAnchor":57},"span empty space",[51,1422,1423],{"x":1413,"y":1389,"style":109,"fill":67,"textAnchor":57},"filter by length",[167,1425,1427],{"className":169,"code":1426,"language":171,"meta":172,"style":172},"triangles = processing.run(\"native:delaunaytriangulation\", {\n    \"INPUT\": stations, \"TOLERANCE\": 0, \"ADD_ATTRIBUTES\": True,\n    \"OUTPUT\": \"memory:gauge_tin\"})[\"OUTPUT\"]\n\nedges = processing.run(\"native:polygonstolines\", {\n    \"INPUT\": triangles, \"OUTPUT\": \"memory:\"})[\"OUTPUT\"]\nexploded = processing.run(\"native:explodelines\", {\n    \"INPUT\": edges, \"OUTPUT\": \"memory:\"})[\"OUTPUT\"]\nunique = processing.run(\"native:deleteduplicategeometries\", {\n    \"INPUT\": exploded, \"OUTPUT\": \"memory:tin_edges\"})[\"OUTPUT\"]\nlengths = sorted(f.geometry().length() for f in unique.getFeatures())\nprint(len(lengths), \"edges; median length\", round(lengths[len(lengths) \u002F\u002F 2]))\n",[174,1428,1429,1443,1468,1483,1487,1501,1520,1534,1553,1567,1587,1608],{"__ignoreMap":172},[177,1430,1431,1434,1436,1438,1441],{"class":179,"line":180},[177,1432,1433],{"class":187},"triangles ",[177,1435,218],{"class":183},[177,1437,245],{"class":187},[177,1439,1440],{"class":224},"\"native:delaunaytriangulation\"",[177,1442,251],{"class":187},[177,1444,1445,1447,1450,1453,1455,1457,1459,1462,1464,1466],{"class":179,"line":191},[177,1446,257],{"class":224},[177,1448,1449],{"class":187},": stations, ",[177,1451,1452],{"class":224},"\"TOLERANCE\"",[177,1454,269],{"class":187},[177,1456,46],{"class":231},[177,1458,572],{"class":187},[177,1460,1461],{"class":224},"\"ADD_ATTRIBUTES\"",[177,1463,269],{"class":187},[177,1465,303],{"class":231},[177,1467,292],{"class":187},[177,1469,1470,1472,1474,1477,1479,1481],{"class":179,"line":205},[177,1471,311],{"class":224},[177,1473,269],{"class":187},[177,1475,1476],{"class":224},"\"memory:gauge_tin\"",[177,1478,324],{"class":187},[177,1480,327],{"class":224},[177,1482,234],{"class":187},[177,1484,1485],{"class":179,"line":212},[177,1486,209],{"emptyLinePlaceholder":208},[177,1488,1489,1492,1494,1496,1499],{"class":179,"line":237},[177,1490,1491],{"class":187},"edges ",[177,1493,218],{"class":183},[177,1495,245],{"class":187},[177,1497,1498],{"class":224},"\"native:polygonstolines\"",[177,1500,251],{"class":187},[177,1502,1503,1505,1508,1510,1512,1514,1516,1518],{"class":179,"line":254},[177,1504,257],{"class":224},[177,1506,1507],{"class":187},": triangles, ",[177,1509,327],{"class":224},[177,1511,269],{"class":187},[177,1513,937],{"class":224},[177,1515,324],{"class":187},[177,1517,327],{"class":224},[177,1519,234],{"class":187},[177,1521,1522,1525,1527,1529,1532],{"class":179,"line":263},[177,1523,1524],{"class":187},"exploded ",[177,1526,218],{"class":183},[177,1528,245],{"class":187},[177,1530,1531],{"class":224},"\"native:explodelines\"",[177,1533,251],{"class":187},[177,1535,1536,1538,1541,1543,1545,1547,1549,1551],{"class":179,"line":282},[177,1537,257],{"class":224},[177,1539,1540],{"class":187},": edges, ",[177,1542,327],{"class":224},[177,1544,269],{"class":187},[177,1546,937],{"class":224},[177,1548,324],{"class":187},[177,1550,327],{"class":224},[177,1552,234],{"class":187},[177,1554,1555,1558,1560,1562,1565],{"class":179,"line":295},[177,1556,1557],{"class":187},"unique ",[177,1559,218],{"class":183},[177,1561,245],{"class":187},[177,1563,1564],{"class":224},"\"native:deleteduplicategeometries\"",[177,1566,251],{"class":187},[177,1568,1569,1571,1574,1576,1578,1581,1583,1585],{"class":179,"line":308},[177,1570,257],{"class":224},[177,1572,1573],{"class":187},": exploded, ",[177,1575,327],{"class":224},[177,1577,269],{"class":187},[177,1579,1580],{"class":224},"\"memory:tin_edges\"",[177,1582,324],{"class":187},[177,1584,327],{"class":224},[177,1586,234],{"class":187},[177,1588,1589,1592,1594,1596,1599,1601,1603,1605],{"class":179,"line":321},[177,1590,1591],{"class":187},"lengths ",[177,1593,218],{"class":183},[177,1595,1275],{"class":231},[177,1597,1598],{"class":187},"(f.geometry().length() ",[177,1600,653],{"class":183},[177,1602,656],{"class":187},[177,1604,659],{"class":183},[177,1606,1607],{"class":187}," unique.getFeatures())\n",[177,1609,1610,1612,1614,1617,1620,1623,1625,1628,1631,1633,1636,1639,1642],{"class":179,"line":332},[177,1611,335],{"class":231},[177,1613,669],{"class":187},[177,1615,1616],{"class":231},"len",[177,1618,1619],{"class":187},"(lengths), ",[177,1621,1622],{"class":224},"\"edges; median length\"",[177,1624,572],{"class":187},[177,1626,1627],{"class":231},"round",[177,1629,1630],{"class":187},"(lengths[",[177,1632,1616],{"class":231},[177,1634,1635],{"class":187},"(lengths) ",[177,1637,1638],{"class":183},"\u002F\u002F",[177,1640,1641],{"class":231}," 2",[177,1643,1299],{"class":187},[14,1645,1646,1648,1649,1652],{},[360,1647,362],{}," Converting triangles to lines and exploding them gives one segment per triangle side; each interior edge appears twice (once per adjacent triangle), so deleting duplicate geometries leaves one per neighbour pair. With ",[174,1650,1651],{},"ADD_ATTRIBUTES",", each triangle records the ids of its three corner points on recent releases, which makes building a neighbour list by id straightforward. The edge-length distribution is a quick diagnostic: long edges at the outside of the point set span empty space and are usually excluded when the triangulation is used for neighbour relations.",[142,1654,1656],{"id":1655},"turn-edges-into-a-neighbour-list","Turn edges into a neighbour list",[14,1658,1659,1660,1664],{},"Spatial statistics such as the ",[21,1661,1663],{"href":1662},"\u002Fspatial-data-processing-automation\u002Fspatial-statistics-and-pattern-analysis\u002Fhotspot-analysis-getis-ord-pyqgis\u002F","Getis-Ord hot spot test"," need to know which features are neighbours. Delaunay edges, with overly long ones removed, are a natural definition.",[167,1666,1668],{"className":169,"code":1667,"language":171,"meta":172,"style":172},"from collections import defaultdict\nfrom qgis.core import QgsSpatialIndex\n\nMAX_EDGE = lengths[int(len(lengths) * 0.95)]      # drop the longest 5 %\nindex = QgsSpatialIndex(stations.getFeatures())\npts = {f.id(): f.geometry() for f in stations.getFeatures()}\n\nneighbours = defaultdict(set)\nfor e in unique.getFeatures():\n    g = e.geometry()\n    if g.length() > MAX_EDGE:\n        continue\n    line = g.asPolyline()\n    a = index.nearestNeighbor(line[0], 1)[0]\n    b = index.nearestNeighbor(line[-1], 1)[0]\n    neighbours[a].add(b)\n    neighbours[b].add(a)\nprint(sum(len(v) for v in neighbours.values()) \u002F max(len(neighbours), 1), \"neighbours on average\")\n",[174,1669,1670,1682,1693,1697,1728,1738,1757,1761,1775,1787,1797,1811,1816,1826,1848,1872,1878,1884],{"__ignoreMap":172},[177,1671,1672,1674,1677,1679],{"class":179,"line":180},[177,1673,194],{"class":183},[177,1675,1676],{"class":187}," collections ",[177,1678,184],{"class":183},[177,1680,1681],{"class":187}," defaultdict\n",[177,1683,1684,1686,1688,1690],{"class":179,"line":191},[177,1685,194],{"class":183},[177,1687,197],{"class":187},[177,1689,184],{"class":183},[177,1691,1692],{"class":187}," QgsSpatialIndex\n",[177,1694,1695],{"class":179,"line":205},[177,1696,209],{"emptyLinePlaceholder":208},[177,1698,1699,1702,1705,1708,1711,1713,1715,1717,1719,1722,1725],{"class":179,"line":212},[177,1700,1701],{"class":231},"MAX_EDGE",[177,1703,1704],{"class":183}," =",[177,1706,1707],{"class":187}," lengths[",[177,1709,1710],{"class":231},"int",[177,1712,669],{"class":187},[177,1714,1616],{"class":231},[177,1716,1635],{"class":187},[177,1718,741],{"class":183},[177,1720,1721],{"class":231}," 0.95",[177,1723,1724],{"class":187},")]      ",[177,1726,1727],{"class":278},"# drop the longest 5 %\n",[177,1729,1730,1733,1735],{"class":179,"line":237},[177,1731,1732],{"class":187},"index ",[177,1734,218],{"class":183},[177,1736,1737],{"class":187}," QgsSpatialIndex(stations.getFeatures())\n",[177,1739,1740,1743,1745,1748,1750,1752,1754],{"class":179,"line":254},[177,1741,1742],{"class":187},"pts ",[177,1744,218],{"class":183},[177,1746,1747],{"class":187}," {f.id(): f.geometry() ",[177,1749,653],{"class":183},[177,1751,656],{"class":187},[177,1753,659],{"class":183},[177,1755,1756],{"class":187}," stations.getFeatures()}\n",[177,1758,1759],{"class":179,"line":263},[177,1760,209],{"emptyLinePlaceholder":208},[177,1762,1763,1766,1768,1770,1773],{"class":179,"line":282},[177,1764,1765],{"class":187},"neighbours ",[177,1767,218],{"class":183},[177,1769,1142],{"class":187},[177,1771,1772],{"class":231},"set",[177,1774,350],{"class":187},[177,1776,1777,1779,1782,1784],{"class":179,"line":295},[177,1778,653],{"class":183},[177,1780,1781],{"class":187}," e ",[177,1783,659],{"class":183},[177,1785,1786],{"class":187}," unique.getFeatures():\n",[177,1788,1789,1792,1794],{"class":179,"line":308},[177,1790,1791],{"class":187},"    g ",[177,1793,218],{"class":183},[177,1795,1796],{"class":187}," e.geometry()\n",[177,1798,1799,1801,1804,1806,1809],{"class":179,"line":321},[177,1800,1207],{"class":183},[177,1802,1803],{"class":187}," g.length() ",[177,1805,1216],{"class":183},[177,1807,1808],{"class":231}," MAX_EDGE",[177,1810,1222],{"class":187},[177,1812,1813],{"class":179,"line":332},[177,1814,1815],{"class":183},"        continue\n",[177,1817,1818,1821,1823],{"class":179,"line":353},[177,1819,1820],{"class":187},"    line ",[177,1822,218],{"class":183},[177,1824,1825],{"class":187}," g.asPolyline()\n",[177,1827,1828,1831,1833,1836,1838,1840,1842,1844,1846],{"class":179,"line":1302},[177,1829,1830],{"class":187},"    a ",[177,1832,218],{"class":183},[177,1834,1835],{"class":187}," index.nearestNeighbor(line[",[177,1837,46],{"class":231},[177,1839,997],{"class":187},[177,1841,1005],{"class":231},[177,1843,228],{"class":187},[177,1845,46],{"class":231},[177,1847,234],{"class":187},[177,1849,1851,1854,1856,1858,1860,1862,1864,1866,1868,1870],{"class":179,"line":1850},15,[177,1852,1853],{"class":187},"    b ",[177,1855,218],{"class":183},[177,1857,1835],{"class":187},[177,1859,1291],{"class":183},[177,1861,1005],{"class":231},[177,1863,997],{"class":187},[177,1865,1005],{"class":231},[177,1867,228],{"class":187},[177,1869,46],{"class":231},[177,1871,234],{"class":187},[177,1873,1875],{"class":179,"line":1874},16,[177,1876,1877],{"class":187},"    neighbours[a].add(b)\n",[177,1879,1881],{"class":179,"line":1880},17,[177,1882,1883],{"class":187},"    neighbours[b].add(a)\n",[177,1885,1887,1889,1891,1894,1896,1898,1901,1903,1906,1908,1911,1913,1916,1918,1920,1923,1925,1928,1931],{"class":179,"line":1886},18,[177,1888,335],{"class":231},[177,1890,669],{"class":187},[177,1892,1893],{"class":231},"sum",[177,1895,669],{"class":187},[177,1897,1616],{"class":231},[177,1899,1900],{"class":187},"(v) ",[177,1902,653],{"class":183},[177,1904,1905],{"class":187}," v ",[177,1907,659],{"class":183},[177,1909,1910],{"class":187}," neighbours.values()) ",[177,1912,783],{"class":183},[177,1914,1915],{"class":231}," max",[177,1917,669],{"class":187},[177,1919,1616],{"class":231},[177,1921,1922],{"class":187},"(neighbours), ",[177,1924,1005],{"class":231},[177,1926,1927],{"class":187},"), ",[177,1929,1930],{"class":224},"\"neighbours on average\"",[177,1932,350],{"class":187},[14,1934,1935,1937],{},[360,1936,362],{}," Each edge's end points coincide with two input points, so a nearest-neighbour lookup recovers their ids. Dropping the longest edges stops points on opposite sides of a gap — across a lake, between two towns — from being treated as neighbours. Delaunay neighbours average about six per point, a well-behaved number for spatial weights.",[142,1939,1941],{"id":1940},"qgis-version-compatibility","QGIS version compatibility",[14,1943,1944,1947,1948,1951,1952,1947,1954,1957,1958,1961,1962,572,1964,1967],{},[174,1945,1946],{},"native:voronoipolygons"," and ",[174,1949,1950],{},"native:delaunaytriangulation"," are the GEOS-based implementations used on QGIS 3.34 LTR, 3.40 LTR and QGIS 4; earlier 3.x releases used ",[174,1953,370],{},[174,1955,1956],{},"qgis:delaunaytriangulation"," with slightly different parameters. Check with ",[174,1959,1960],{},"processing.algorithmHelp(...)"," on your version. ",[174,1963,444],{},[174,1965,1966],{},"native:fieldcalculator"," and the line algorithms are stable across all.",[142,1969,1971],{"id":1970},"troubleshooting","Troubleshooting",[147,1973,1974,1980,1986,1992],{},[150,1975,1976,1979],{},[360,1977,1978],{},"Fewer cells than points."," Duplicate or near-coincident points; remove duplicates or set a tolerance.",[150,1981,1982,1985],{},[360,1983,1984],{},"Cells look stretched."," The layer is in a geographic CRS; reproject first.",[150,1987,1988,1991],{},[360,1989,1990],{},"Allocation ignores barriers."," Voronoi uses straight-line distance; use network analysis instead.",[150,1993,1994,1997],{},[360,1995,1996],{},"Neighbour lists link distant points."," Long outer edges were kept; filter by length.",[142,1999,2001],{"id":2000},"conclusion","Conclusion",[14,2003,2004,2005,2007],{},"Build Voronoi cells with ",[174,2006,1946],{},", clip them to the real study area and recompute areas, use them for Thiessen weighting and fast nearest-site allocation, build Delaunay triangles for neighbour relationships and surfaces, and filter long edges before treating them as neighbours.",[142,2009,2011],{"id":2010},"frequently-asked-questions","Frequently Asked Questions",[14,2013,2014,2017],{},[360,2015,2016],{},"Are Thiessen and Voronoi polygons the same?","\nYes. \"Thiessen polygons\" is the name used in hydrology and climatology for the same construction.",[14,2019,2020,2023,2024,2027],{},[360,2021,2022],{},"Can I weight Voronoi cells by site capacity?","\nNot with this algorithm; weighted (power) diagrams need a library such as ",[174,2025,2026],{},"scipy"," or a custom implementation.",[14,2029,2030,2033,2034,2037],{},[360,2031,2032],{},"How do I get a TIN surface from the triangles?","\nUse ",[21,2035,1407],{"href":2036},"\u002Fspatial-data-processing-automation\u002Fterrain-and-interpolation-analysis\u002Fbuild-tin-interpolation-pyqgis\u002F",", which builds and rasterises the triangulation in one step.",[14,2039,2040,2043],{},[360,2041,2042],{},"Do the cells change if I add a point?","\nOnly the cells around the new point change, but the algorithm rebuilds everything; rerun it.",[142,2045,2047],{"id":2046},"related","Related",[147,2049,2050,2055,2060,2066,2071],{},[150,2051,2052,2054],{},[21,2053,24],{"href":23}," — the guide this recipe belongs to",[150,2056,2057],{},[21,2058,2059],{"href":1662},"Run a Hot Spot Analysis with Getis-Ord Gi* in PyQGIS",[150,2061,2062],{},[21,2063,2065],{"href":2064},"\u002Fspatial-data-processing-automation\u002Fspatial-statistics-and-pattern-analysis\u002Fnearest-neighbour-analysis-pyqgis\u002F","Run a Nearest Neighbour Analysis in PyQGIS",[150,2067,2068],{},[21,2069,2070],{"href":2036},"Build a TIN Interpolation in PyQGIS",[150,2072,2073],{},[21,2074,2076],{"href":2075},"\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Fspatial-join-points-in-polygons-pyqgis\u002F","Spatial Join Points in Polygons in PyQGIS",[2078,2079,2080],"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);}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":172,"searchDepth":191,"depth":191,"links":2082},[2083,2084,2085,2086,2087,2088,2089,2090,2091,2092,2093,2094,2095],{"id":144,"depth":191,"text":145},{"id":161,"depth":191,"text":162},{"id":374,"depth":191,"text":375},{"id":713,"depth":191,"text":714},{"id":840,"depth":191,"text":841},{"id":1040,"depth":191,"text":1041},{"id":1348,"depth":191,"text":1349},{"id":1655,"depth":191,"text":1656},{"id":1940,"depth":191,"text":1941},{"id":1970,"depth":191,"text":1971},{"id":2000,"depth":191,"text":2001},{"id":2010,"depth":191,"text":2011},{"id":2046,"depth":191,"text":2047},"Build Voronoi (Thiessen) polygons and Delaunay triangulations from points with Processing — clipping cells to a study area, carrying attributes across, using cells as catchments and for proximity allocation, and extracting neighbour relationships from the triangulation.","md",{"slug":2099,"type":2100,"breadcrumb":2101,"datePublished":2102,"dateModified":2102},"create-voronoi-and-delaunay-pyqgis","article","Voronoi Polygons and Delaunay Triangles","2026-10-02","\u002Fspatial-data-processing-automation\u002Fspatial-statistics-and-pattern-analysis\u002Fcreate-voronoi-and-delaunay-pyqgis",{"title":5,"description":2096},"spatial-data-processing-automation\u002Fspatial-statistics-and-pattern-analysis\u002Fcreate-voronoi-and-delaunay-pyqgis\u002Findex","HlaWLfqH7UalXbXX-EGMzai6OQqvMQmYBwEDvXDs0cw",1790966264898]