[{"data":1,"prerenderedAt":1520},["ShallowReactive",2],{"doc:\u002Fspatial-data-processing-automation\u002Fterrain-and-interpolation-analysis\u002Fidw-interpolation-from-points-pyqgis":3},{"id":4,"title":5,"body":6,"description":1509,"extension":1510,"meta":1511,"navigation":245,"path":1516,"seo":1517,"stem":1518,"__hash__":1519},"docs\u002Fspatial-data-processing-automation\u002Fterrain-and-interpolation-analysis\u002Fidw-interpolation-from-points-pyqgis\u002Findex.md","IDW Interpolation from Points in PyQGIS",{"type":7,"value":8,"toc":1496},"minimark",[9,13,17,26,179,184,205,209,212,365,390,396,400,403,522,539,546,550,553,676,681,684,687,691,694,858,867,956,960,963,1228,1245,1249,1255,1342,1346,1396,1400,1403,1407,1419,1436,1447,1458,1462,1492],[10,11,5],"h1",{"id":12},"idw-interpolation-from-points-in-pyqgis",[14,15,16],"p",{},"Inverse distance weighting fills the space between measurements by averaging nearby samples, weighted so that closer ones count for more. It makes very few assumptions, which is its strength, and it will happily produce a confident-looking value fifty kilometres from the nearest observation, which is its weakness. Getting a useful surface out of it is mostly about constraining where it is allowed to speak.",[14,18,19,20,25],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fspatial-data-processing-automation\u002Fterrain-and-interpolation-analysis\u002F","Terrain & Interpolation Analysis in PyQGIS",". It covers the awkward layer-specification string the algorithm expects, choosing the distance power and pixel size, masking the output to where the data supports it, and cross-validating so the error has a number attached.",[14,27,28],{},[29,30,35,39,43,50,59,69,75,81,88,92,96,99,106,111,117,122,128,131,136,141,145,152,157,160,165,168,173],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"xmlns":34},"0 0 760 316","img","Inverse distance weighting at one output cell: nearby samples contribute strongly, distant samples weakly, and the weights depend on the chosen power","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[36,37,38],"title",{},"How one interpolated cell gets its value",[40,41,42],"desc",{},"An output cell is surrounded by sample points at different distances. Each sample contributes a weight proportional to one over its distance raised to the chosen power. A power of one spreads influence broadly; a power of four concentrates it on the nearest sample, producing plateaus around each observation.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","316","#f6f3ea",[51,52,58],"text",{"x":53,"y":54,"style":55,"fill":56,"textAnchor":57},"380","28","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","The power decides how quickly influence dies away",[44,60],{"x":61,"y":62,"width":63,"height":64,"rx":65,"fill":66,"stroke":67,"style":68},"24","52","330","230","10","#fffdf7","#0f766e","stroke-width:2.5",[51,70,74],{"x":71,"y":72,"style":73,"fill":67,"textAnchor":57},"189","78","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","one output cell",[44,76],{"x":77,"y":78,"width":79,"height":79,"fill":80,"stroke":67,"style":68},"176","160","26","#eef7f4",[82,83],"circle",{"cx":84,"cy":85,"r":86,"fill":87},"112","128","6","#2563eb",[82,89],{"cx":90,"cy":91,"r":86,"fill":87},"252","122",[82,93],{"cx":94,"cy":95,"r":86,"fill":87},"96","228",[82,97],{"cx":98,"cy":64,"r":86,"fill":87},"286",[100,101],"line",{"x1":77,"y1":102,"x2":103,"y2":104,"stroke":87,"style":105},"166","118","134","stroke-width:3",[100,107],{"x1":108,"y1":102,"x2":109,"y2":85,"stroke":87,"style":110},"202","246","stroke-width:2.2",[100,112],{"x1":77,"y1":113,"x2":114,"y2":115,"stroke":87,"style":116},"186","102","222","stroke-width:1.4",[100,118],{"x1":108,"y1":113,"x2":119,"y2":120,"stroke":87,"style":121},"280","224","stroke-width:1",[51,123,127],{"x":124,"y":103,"style":125,"fill":126},"130","font-size:9.5px;font-family:sans-serif","#2f3b35","near — heavy",[51,129,130],{"x":90,"y":90,"style":125,"fill":126},"far — light",[51,132,135],{"x":71,"y":133,"style":134,"fill":126,"textAnchor":57},"270","text-anchor:middle;font-size:10.5px;font-family:monospace","w = 1 \u002F dᵖ",[44,137],{"x":138,"y":62,"width":139,"height":64,"rx":65,"fill":66,"stroke":140,"style":68},"398","338","#b45309",[51,142,144],{"x":143,"y":72,"style":73,"fill":140,"textAnchor":57},"567","the same data, three powers",[146,147],"path",{"d":148,"fill":149,"stroke":150,"style":151},"M424 200 Q470 168 520 190 T620 176 T712 196","none","#15803d","stroke-width:2.4",[51,153,156],{"x":154,"y":155,"style":125,"fill":150},"642","150","p = 1 · broad",[146,158],{"d":159,"fill":149,"stroke":87,"style":110},"M424 214 Q460 154 500 210 Q540 156 580 208 Q624 152 664 206 Q690 180 712 206",[51,161,164],{"x":162,"y":163,"style":125,"fill":87},"428","140","p = 2 · default",[146,166],{"d":167,"fill":149,"stroke":140,"style":110},"M424 240 H452 V196 H484 V240 H520 V190 H556 V240 H596 V194 H636 V240 H672 V200 H712",[51,169,172],{"x":170,"y":171,"style":125,"fill":140},"470","262","p = 4 · plateaus",[51,174,178],{"x":53,"y":175,"style":176,"fill":177,"textAnchor":57},"304","text-anchor:middle;font-size:11px;font-family:sans-serif","#59645f","IDW never produces a value outside the range of the observations",[180,181,183],"h2",{"id":182},"prerequisites","Prerequisites",[185,186,187,195,202],"ul",{},[188,189,190,194],"li",{},[191,192,193],"strong",{},"QGIS 3.34 LTR"," (bundled Python 3.12) or newer.",[188,196,197,198,201],{},"A point layer with a numeric attribute to interpolate, in a ",[191,199,200],{},"projected"," CRS. Distances in degrees weight north–south and east–west differently and skew the surface.",[188,203,204],{},"Enough samples to be worth interpolating. Below roughly thirty points, a surface implies far more than the data can support.",[180,206,208],{"id":207},"build-the-interpolation-data-string","Build the interpolation data string",[14,210,211],{},"The algorithm takes its input as an encoded string rather than as parameters, which is the only genuinely unpleasant part of the process.",[213,214,219],"pre",{"className":215,"code":216,"language":217,"meta":218,"style":218},"language-python shiki shiki-themes github-dark","from qgis.core import QgsProject\n\nlayer = QgsProject.instance().mapLayersByName(\"rain_gauges\")[0]\nattribute_index = layer.fields().indexOf(\"annual_mm\")\nif attribute_index \u003C 0:\n    raise LookupError(\"field 'annual_mm' not found on the layer\")\n\nspec = f\"{layer.source()}::~::0::~::{attribute_index}::~::0\"\n","python","",[220,221,222,240,247,272,289,307,324,329],"code",{"__ignoreMap":218},[223,224,226,230,234,237],"span",{"class":100,"line":225},1,[223,227,229],{"class":228},"snl16","from",[223,231,233],{"class":232},"s95oV"," qgis.core ",[223,235,236],{"class":228},"import",[223,238,239],{"class":232}," QgsProject\n",[223,241,243],{"class":100,"line":242},2,[223,244,246],{"emptyLinePlaceholder":245},true,"\n",[223,248,250,253,256,259,263,266,269],{"class":100,"line":249},3,[223,251,252],{"class":232},"layer ",[223,254,255],{"class":228},"=",[223,257,258],{"class":232}," QgsProject.instance().mapLayersByName(",[223,260,262],{"class":261},"sU2Wk","\"rain_gauges\"",[223,264,265],{"class":232},")[",[223,267,46],{"class":268},"sDLfK",[223,270,271],{"class":232},"]\n",[223,273,275,278,280,283,286],{"class":100,"line":274},4,[223,276,277],{"class":232},"attribute_index ",[223,279,255],{"class":228},[223,281,282],{"class":232}," layer.fields().indexOf(",[223,284,285],{"class":261},"\"annual_mm\"",[223,287,288],{"class":232},")\n",[223,290,292,295,298,301,304],{"class":100,"line":291},5,[223,293,294],{"class":228},"if",[223,296,297],{"class":232}," attribute_index ",[223,299,300],{"class":228},"\u003C",[223,302,303],{"class":268}," 0",[223,305,306],{"class":232},":\n",[223,308,310,313,316,319,322],{"class":100,"line":309},6,[223,311,312],{"class":228},"    raise",[223,314,315],{"class":268}," LookupError",[223,317,318],{"class":232},"(",[223,320,321],{"class":261},"\"field 'annual_mm' not found on the layer\"",[223,323,288],{"class":232},[223,325,327],{"class":100,"line":326},7,[223,328,246],{"emptyLinePlaceholder":245},[223,330,332,335,337,340,343,346,349,352,355,357,360,362],{"class":100,"line":331},8,[223,333,334],{"class":232},"spec ",[223,336,255],{"class":228},[223,338,339],{"class":228}," f",[223,341,342],{"class":261},"\"",[223,344,345],{"class":268},"{",[223,347,348],{"class":232},"layer.source()",[223,350,351],{"class":268},"}",[223,353,354],{"class":261},"::~::0::~::",[223,356,345],{"class":268},[223,358,359],{"class":232},"attribute_index",[223,361,351],{"class":268},[223,363,364],{"class":261},"::~::0\"\n",[14,366,367,370,371,374,375,378,379,382,383,385,386,389],{},[191,368,369],{},"Breakdown:"," The four ",[220,372,373],{},"::~::","-separated fields are the layer source, the geometry-type flag (0 for points), the zero-based index of the attribute to interpolate, and a use-Z flag (0 to use the attribute, 1 to use the geometry's Z instead). ",[220,376,377],{},"fields().indexOf()"," returns ",[220,380,381],{},"-1"," for a missing field, and passing ",[220,384,381],{}," into the spec produces an empty raster rather than an error — so the guard above is not optional. Multiple layers can be interpolated together by joining several specs with ",[220,387,388],{},";",", which is how spot heights and contour vertices are combined into one surface.",[14,391,392,393,395],{},"Using ",[220,394,348],{}," rather than a hard-coded path keeps the script working when the layer came from a GeoPackage with a layer name appended, which a plain path would lose.",[180,397,399],{"id":398},"run-the-interpolation","Run the interpolation",[14,401,402],{},"With the spec built, the call itself is straightforward.",[213,404,406],{"className":215,"code":405,"language":217,"meta":218,"style":218},"import processing\n\nextent = layer.extent()\nextent.grow(500)          # 500 m of margin, in layer units\n\nprocessing.run(\"qgis:idwinterpolation\", {\n    \"INTERPOLATION_DATA\": spec,\n    \"DISTANCE_COEFFICIENT\": 2.0,\n    \"EXTENT\": extent,\n    \"PIXEL_SIZE\": 100,\n    \"OUTPUT\": \"\u002Fdata\u002Foutput\u002Frainfall_idw.tif\",\n})\n",[220,407,408,415,419,429,444,448,459,467,481,490,503,516],{"__ignoreMap":218},[223,409,410,412],{"class":100,"line":225},[223,411,236],{"class":228},[223,413,414],{"class":232}," processing\n",[223,416,417],{"class":100,"line":242},[223,418,246],{"emptyLinePlaceholder":245},[223,420,421,424,426],{"class":100,"line":249},[223,422,423],{"class":232},"extent ",[223,425,255],{"class":228},[223,427,428],{"class":232}," layer.extent()\n",[223,430,431,434,437,440],{"class":100,"line":274},[223,432,433],{"class":232},"extent.grow(",[223,435,436],{"class":268},"500",[223,438,439],{"class":232},")          ",[223,441,443],{"class":442},"sjoCn","# 500 m of margin, in layer units\n",[223,445,446],{"class":100,"line":291},[223,447,246],{"emptyLinePlaceholder":245},[223,449,450,453,456],{"class":100,"line":309},[223,451,452],{"class":232},"processing.run(",[223,454,455],{"class":261},"\"qgis:idwinterpolation\"",[223,457,458],{"class":232},", {\n",[223,460,461,464],{"class":100,"line":326},[223,462,463],{"class":261},"    \"INTERPOLATION_DATA\"",[223,465,466],{"class":232},": spec,\n",[223,468,469,472,475,478],{"class":100,"line":331},[223,470,471],{"class":261},"    \"DISTANCE_COEFFICIENT\"",[223,473,474],{"class":232},": ",[223,476,477],{"class":268},"2.0",[223,479,480],{"class":232},",\n",[223,482,484,487],{"class":100,"line":483},9,[223,485,486],{"class":261},"    \"EXTENT\"",[223,488,489],{"class":232},": extent,\n",[223,491,493,496,498,501],{"class":100,"line":492},10,[223,494,495],{"class":261},"    \"PIXEL_SIZE\"",[223,497,474],{"class":232},[223,499,500],{"class":268},"100",[223,502,480],{"class":232},[223,504,506,509,511,514],{"class":100,"line":505},11,[223,507,508],{"class":261},"    \"OUTPUT\"",[223,510,474],{"class":232},[223,512,513],{"class":261},"\"\u002Fdata\u002Foutput\u002Frainfall_idw.tif\"",[223,515,480],{"class":232},[223,517,519],{"class":100,"line":518},12,[223,520,521],{"class":232},"})\n",[14,523,524,526,527,530,531,534,535,538],{},[191,525,369],{}," ",[220,528,529],{},"DISTANCE_COEFFICIENT"," is the power ",[532,533,14],"em",{},". At 2 — the near-universal default — a sample twice as far away counts a quarter as much, which produces a surface that is smooth without ignoring local detail. ",[220,536,537],{},"PIXEL_SIZE"," is in layer units and should be no finer than roughly a quarter of the mean spacing between samples; going finer produces a beautifully smooth raster whose extra detail is entirely manufactured. Growing the extent slightly gives room for the mask step below without the surface being clipped by its own bounding box.",[14,540,541,542,545],{},"QGIS's IDW uses every sample for every cell — there is no search radius or neighbour limit in this algorithm. That makes it predictable and slow: cost grows with points multiplied by cells, so ten thousand samples on a fine grid is genuinely expensive. Where a neighbour limit is needed, ",[220,543,544],{},"gdal:gridinversedistancenearestneighbor"," provides one.",[180,547,549],{"id":548},"choosing-the-power-deliberately","Choosing the power deliberately",[14,551,552],{},"The distance power is the one parameter that changes the story the surface tells, and two minutes of comparison settles it better than any rule of thumb.",[213,554,556],{"className":215,"code":555,"language":217,"meta":218,"style":218},"import processing\n\nfor power in (1.0, 2.0, 3.0, 5.0):\n    processing.run(\"qgis:idwinterpolation\", {\n        \"INTERPOLATION_DATA\": spec,\n        \"DISTANCE_COEFFICIENT\": power,\n        \"EXTENT\": extent,\n        \"PIXEL_SIZE\": 100,\n        \"OUTPUT\": f\"\u002Fdata\u002Foutput\u002Fidw_p{int(power)}.tif\",\n    })\n",[220,557,558,564,568,603,612,619,627,634,645,671],{"__ignoreMap":218},[223,559,560,562],{"class":100,"line":225},[223,561,236],{"class":228},[223,563,414],{"class":232},[223,565,566],{"class":100,"line":242},[223,567,246],{"emptyLinePlaceholder":245},[223,569,570,573,576,579,582,585,588,590,592,595,597,600],{"class":100,"line":249},[223,571,572],{"class":228},"for",[223,574,575],{"class":232}," power ",[223,577,578],{"class":228},"in",[223,580,581],{"class":232}," (",[223,583,584],{"class":268},"1.0",[223,586,587],{"class":232},", ",[223,589,477],{"class":268},[223,591,587],{"class":232},[223,593,594],{"class":268},"3.0",[223,596,587],{"class":232},[223,598,599],{"class":268},"5.0",[223,601,602],{"class":232},"):\n",[223,604,605,608,610],{"class":100,"line":274},[223,606,607],{"class":232},"    processing.run(",[223,609,455],{"class":261},[223,611,458],{"class":232},[223,613,614,617],{"class":100,"line":291},[223,615,616],{"class":261},"        \"INTERPOLATION_DATA\"",[223,618,466],{"class":232},[223,620,621,624],{"class":100,"line":309},[223,622,623],{"class":261},"        \"DISTANCE_COEFFICIENT\"",[223,625,626],{"class":232},": power,\n",[223,628,629,632],{"class":100,"line":326},[223,630,631],{"class":261},"        \"EXTENT\"",[223,633,489],{"class":232},[223,635,636,639,641,643],{"class":100,"line":331},[223,637,638],{"class":261},"        \"PIXEL_SIZE\"",[223,640,474],{"class":232},[223,642,500],{"class":268},[223,644,480],{"class":232},[223,646,647,650,652,655,658,661,664,666,669],{"class":100,"line":483},[223,648,649],{"class":261},"        \"OUTPUT\"",[223,651,474],{"class":232},[223,653,654],{"class":228},"f",[223,656,657],{"class":261},"\"\u002Fdata\u002Foutput\u002Fidw_p",[223,659,660],{"class":268},"{int",[223,662,663],{"class":232},"(power)",[223,665,351],{"class":268},[223,667,668],{"class":261},".tif\"",[223,670,480],{"class":232},[223,672,673],{"class":100,"line":492},[223,674,675],{"class":232},"    })\n",[14,677,678,680],{},[191,679,369],{}," Writing four rasters rather than one costs a few minutes of compute and gives something to look at side by side. At a power of 1 the surface tends towards a broad regional average and local measurements barely register. At 5 each sample sits on its own plateau with steep walls between, and the map reads as a set of discs rather than a surface — a pattern usually described as bull's-eyeing, and a reliable sign the power is too high for the sample density.",[14,682,683],{},"The choice is not purely aesthetic. A higher power is defensible when the phenomenon genuinely changes sharply over short distances — contamination around point sources, noise around a runway. A lower power suits something that varies gradually and where each sample is a noisy estimate of a smooth field, such as annual rainfall. Where you cannot argue for either, 2 is the convention and requires no defence.",[14,685,686],{},"One thing the power cannot do is make the surface exceed the data. IDW is a weighted average, so every output value lies between the minimum and maximum observation. A summit higher than the highest spot height, or a hollow deeper than the deepest borehole, will never appear — which is a genuine limitation when interpolating terrain and a useful safety property when interpolating measurements.",[180,688,690],{"id":689},"mask-the-output-to-where-the-data-supports-it","Mask the output to where the data supports it",[14,692,693],{},"This is the step that turns a plausible picture into a defensible one, and it is the step almost everyone skips.",[213,695,697],{"className":215,"code":696,"language":217,"meta":218,"style":218},"buffered = processing.run(\"native:buffer\", {\n    \"INPUT\": layer,\n    \"DISTANCE\": 5000,                  # a defensible influence radius\n    \"SEGMENTS\": 12,\n    \"DISSOLVE\": True,\n    \"OUTPUT\": \"TEMPORARY_OUTPUT\",\n})[\"OUTPUT\"]\n\nprocessing.run(\"gdal:cliprasterbymasklayer\", {\n    \"INPUT\": \"\u002Fdata\u002Foutput\u002Frainfall_idw.tif\",\n    \"MASK\": buffered,\n    \"NODATA\": -9999,\n    \"CROP_TO_CUTLINE\": True,\n    \"OUTPUT\": \"\u002Fdata\u002Foutput\u002Frainfall_idw_masked.tif\",\n})\n",[220,698,699,714,722,738,750,762,773,783,787,796,806,814,829,841,853],{"__ignoreMap":218},[223,700,701,704,706,709,712],{"class":100,"line":225},[223,702,703],{"class":232},"buffered ",[223,705,255],{"class":228},[223,707,708],{"class":232}," processing.run(",[223,710,711],{"class":261},"\"native:buffer\"",[223,713,458],{"class":232},[223,715,716,719],{"class":100,"line":242},[223,717,718],{"class":261},"    \"INPUT\"",[223,720,721],{"class":232},": layer,\n",[223,723,724,727,729,732,735],{"class":100,"line":249},[223,725,726],{"class":261},"    \"DISTANCE\"",[223,728,474],{"class":232},[223,730,731],{"class":268},"5000",[223,733,734],{"class":232},",                  ",[223,736,737],{"class":442},"# a defensible influence radius\n",[223,739,740,743,745,748],{"class":100,"line":274},[223,741,742],{"class":261},"    \"SEGMENTS\"",[223,744,474],{"class":232},[223,746,747],{"class":268},"12",[223,749,480],{"class":232},[223,751,752,755,757,760],{"class":100,"line":291},[223,753,754],{"class":261},"    \"DISSOLVE\"",[223,756,474],{"class":232},[223,758,759],{"class":268},"True",[223,761,480],{"class":232},[223,763,764,766,768,771],{"class":100,"line":309},[223,765,508],{"class":261},[223,767,474],{"class":232},[223,769,770],{"class":261},"\"TEMPORARY_OUTPUT\"",[223,772,480],{"class":232},[223,774,775,778,781],{"class":100,"line":326},[223,776,777],{"class":232},"})[",[223,779,780],{"class":261},"\"OUTPUT\"",[223,782,271],{"class":232},[223,784,785],{"class":100,"line":331},[223,786,246],{"emptyLinePlaceholder":245},[223,788,789,791,794],{"class":100,"line":483},[223,790,452],{"class":232},[223,792,793],{"class":261},"\"gdal:cliprasterbymasklayer\"",[223,795,458],{"class":232},[223,797,798,800,802,804],{"class":100,"line":492},[223,799,718],{"class":261},[223,801,474],{"class":232},[223,803,513],{"class":261},[223,805,480],{"class":232},[223,807,808,811],{"class":100,"line":505},[223,809,810],{"class":261},"    \"MASK\"",[223,812,813],{"class":232},": buffered,\n",[223,815,816,819,821,824,827],{"class":100,"line":518},[223,817,818],{"class":261},"    \"NODATA\"",[223,820,474],{"class":232},[223,822,823],{"class":228},"-",[223,825,826],{"class":268},"9999",[223,828,480],{"class":232},[223,830,832,835,837,839],{"class":100,"line":831},13,[223,833,834],{"class":261},"    \"CROP_TO_CUTLINE\"",[223,836,474],{"class":232},[223,838,759],{"class":268},[223,840,480],{"class":232},[223,842,844,846,848,851],{"class":100,"line":843},14,[223,845,508],{"class":261},[223,847,474],{"class":232},[223,849,850],{"class":261},"\"\u002Fdata\u002Foutput\u002Frainfall_idw_masked.tif\"",[223,852,480],{"class":232},[223,854,856],{"class":100,"line":855},15,[223,857,521],{"class":232},[14,859,860,862,863,866],{},[191,861,369],{}," Buffering every sample by a distance you can justify, dissolving the result, and clipping to it converts the implicit claim \"this surface is valid everywhere\" into the explicit claim \"this surface is valid within 5 km of a gauge\". The buffer distance is a judgement about the phenomenon — rainfall varies over kilometres, soil chemistry over metres — and writing it as a named constant with a comment is worth more than any amount of parameter tuning. ",[220,864,865],{},"DISSOLVE: True"," merges overlapping buffers so the mask is one polygon rather than hundreds.",[14,868,869],{},[29,870,873,876,879,882,885,889,893,898,902,905,909,913,919,924,927,931,935,938,940,943,946,949,953],{"viewBox":871,"role":32,"ariaLabel":872,"xmlns":34},"0 0 760 296","An unmasked interpolation covering the whole rectangular extent compared with the same surface clipped to buffers around the sample points, so areas far from any sample are left empty",[36,874,875],{},"Masking states what the surface actually covers",[40,877,878],{},"The unmasked interpolation fills its entire rectangular extent with values, including large areas with no nearby sample. Clipping to a dissolved buffer around the sample points removes those areas, so the map shows values only where an observation is within a justified distance.",[44,880],{"x":46,"y":46,"width":47,"height":881,"fill":49},"296",[51,883,884],{"x":53,"y":54,"style":55,"fill":56,"textAnchor":57},"The gaps are information, not a defect",[44,886],{"x":79,"y":62,"width":63,"height":887,"rx":65,"fill":66,"stroke":888,"style":68},"204","#b91c1c",[51,890,892],{"x":891,"y":72,"style":73,"fill":888,"textAnchor":57},"191","unmasked",[44,894],{"x":62,"y":895,"width":896,"height":85,"fill":80,"stroke":67,"style":897},"94","278","stroke-width:1.6",[82,899],{"cx":900,"cy":85,"r":901,"fill":87},"92","4",[82,903],{"cx":904,"cy":102,"r":901,"fill":87},"132",[82,906],{"cx":907,"cy":908,"r":901,"fill":87},"106","196",[82,910],{"cx":911,"cy":912,"r":901,"fill":87},"168","120",[51,914,918],{"x":915,"y":916,"style":917,"fill":888,"textAnchor":57},"256","164","text-anchor:middle;font-size:10px;font-family:sans-serif","values here too",[51,920,923],{"x":891,"y":921,"style":922,"fill":126,"textAnchor":57},"242","text-anchor:middle;font-size:10.5px;font-family:sans-serif","no sample within 40 km, but a number anyway",[44,925],{"x":926,"y":62,"width":63,"height":887,"rx":65,"fill":66,"stroke":150,"style":68},"404",[51,928,930],{"x":929,"y":72,"style":73,"fill":150,"textAnchor":57},"569","masked to buffered samples",[44,932],{"x":933,"y":895,"width":896,"height":85,"fill":149,"stroke":177,"style":934},"430","stroke-width:1.4;stroke-dasharray:5 4",[146,936],{"d":937,"fill":80,"stroke":67,"style":897},"M470 128 a34 34 0 1 0 0.1 0 M510 166 a34 34 0 1 0 0.1 0 M484 196 a34 34 0 1 0 0.1 0 M546 120 a34 34 0 1 0 0.1 0",[82,939],{"cx":170,"cy":85,"r":901,"fill":87},[82,941],{"cx":942,"cy":102,"r":901,"fill":87},"510",[82,944],{"cx":945,"cy":908,"r":901,"fill":87},"484",[82,947],{"cx":948,"cy":912,"r":901,"fill":87},"546",[51,950,952],{"x":951,"y":916,"style":917,"fill":150,"textAnchor":57},"640","nodata",[51,954,955],{"x":929,"y":921,"style":922,"fill":126,"textAnchor":57},"the map now says where it does not know",[180,957,959],{"id":958},"cross-validate-so-the-error-has-a-number","Cross-validate, so the error has a number",[14,961,962],{},"Holding samples back and testing against them converts an opinion about quality into a statistic.",[213,964,966],{"className":215,"code":965,"language":217,"meta":218,"style":218},"import processing\n\nholdout = processing.run(\"native:randomextract\", {\n    \"INPUT\": layer,\n    \"METHOD\": 1,                       # percentage\n    \"NUMBER\": 10,\n    \"OUTPUT\": \"TEMPORARY_OUTPUT\",\n})[\"OUTPUT\"]\n\nsampled = processing.run(\"native:rastersampling\", {\n    \"INPUT\": holdout,\n    \"RASTERCOPY\": \"\u002Fdata\u002Foutput\u002Frainfall_idw.tif\",\n    \"COLUMN_PREFIX\": \"pred_\",\n    \"OUTPUT\": \"TEMPORARY_OUTPUT\",\n})[\"OUTPUT\"]\n\nerrors = [\n    abs(f[\"annual_mm\"] - f[\"pred_1\"])\n    for f in sampled.getFeatures()\n    if f[\"pred_1\"] is not None\n]\nprint(f\"mean absolute error: {sum(errors) \u002F len(errors):.1f} mm\")\n",[220,967,968,974,978,992,998,1014,1025,1035,1043,1047,1061,1068,1079,1091,1101,1109,1114,1125,1150,1164,1185,1190],{"__ignoreMap":218},[223,969,970,972],{"class":100,"line":225},[223,971,236],{"class":228},[223,973,414],{"class":232},[223,975,976],{"class":100,"line":242},[223,977,246],{"emptyLinePlaceholder":245},[223,979,980,983,985,987,990],{"class":100,"line":249},[223,981,982],{"class":232},"holdout ",[223,984,255],{"class":228},[223,986,708],{"class":232},[223,988,989],{"class":261},"\"native:randomextract\"",[223,991,458],{"class":232},[223,993,994,996],{"class":100,"line":274},[223,995,718],{"class":261},[223,997,721],{"class":232},[223,999,1000,1003,1005,1008,1011],{"class":100,"line":291},[223,1001,1002],{"class":261},"    \"METHOD\"",[223,1004,474],{"class":232},[223,1006,1007],{"class":268},"1",[223,1009,1010],{"class":232},",                       ",[223,1012,1013],{"class":442},"# percentage\n",[223,1015,1016,1019,1021,1023],{"class":100,"line":309},[223,1017,1018],{"class":261},"    \"NUMBER\"",[223,1020,474],{"class":232},[223,1022,65],{"class":268},[223,1024,480],{"class":232},[223,1026,1027,1029,1031,1033],{"class":100,"line":326},[223,1028,508],{"class":261},[223,1030,474],{"class":232},[223,1032,770],{"class":261},[223,1034,480],{"class":232},[223,1036,1037,1039,1041],{"class":100,"line":331},[223,1038,777],{"class":232},[223,1040,780],{"class":261},[223,1042,271],{"class":232},[223,1044,1045],{"class":100,"line":483},[223,1046,246],{"emptyLinePlaceholder":245},[223,1048,1049,1052,1054,1056,1059],{"class":100,"line":492},[223,1050,1051],{"class":232},"sampled ",[223,1053,255],{"class":228},[223,1055,708],{"class":232},[223,1057,1058],{"class":261},"\"native:rastersampling\"",[223,1060,458],{"class":232},[223,1062,1063,1065],{"class":100,"line":505},[223,1064,718],{"class":261},[223,1066,1067],{"class":232},": holdout,\n",[223,1069,1070,1073,1075,1077],{"class":100,"line":518},[223,1071,1072],{"class":261},"    \"RASTERCOPY\"",[223,1074,474],{"class":232},[223,1076,513],{"class":261},[223,1078,480],{"class":232},[223,1080,1081,1084,1086,1089],{"class":100,"line":831},[223,1082,1083],{"class":261},"    \"COLUMN_PREFIX\"",[223,1085,474],{"class":232},[223,1087,1088],{"class":261},"\"pred_\"",[223,1090,480],{"class":232},[223,1092,1093,1095,1097,1099],{"class":100,"line":843},[223,1094,508],{"class":261},[223,1096,474],{"class":232},[223,1098,770],{"class":261},[223,1100,480],{"class":232},[223,1102,1103,1105,1107],{"class":100,"line":855},[223,1104,777],{"class":232},[223,1106,780],{"class":261},[223,1108,271],{"class":232},[223,1110,1112],{"class":100,"line":1111},16,[223,1113,246],{"emptyLinePlaceholder":245},[223,1115,1117,1120,1122],{"class":100,"line":1116},17,[223,1118,1119],{"class":232},"errors ",[223,1121,255],{"class":228},[223,1123,1124],{"class":232}," [\n",[223,1126,1128,1131,1134,1136,1139,1141,1144,1147],{"class":100,"line":1127},18,[223,1129,1130],{"class":268},"    abs",[223,1132,1133],{"class":232},"(f[",[223,1135,285],{"class":261},[223,1137,1138],{"class":232},"] ",[223,1140,823],{"class":228},[223,1142,1143],{"class":232}," f[",[223,1145,1146],{"class":261},"\"pred_1\"",[223,1148,1149],{"class":232},"])\n",[223,1151,1153,1156,1159,1161],{"class":100,"line":1152},19,[223,1154,1155],{"class":228},"    for",[223,1157,1158],{"class":232}," f ",[223,1160,578],{"class":228},[223,1162,1163],{"class":232}," sampled.getFeatures()\n",[223,1165,1167,1170,1172,1174,1176,1179,1182],{"class":100,"line":1166},20,[223,1168,1169],{"class":228},"    if",[223,1171,1143],{"class":232},[223,1173,1146],{"class":261},[223,1175,1138],{"class":232},[223,1177,1178],{"class":228},"is",[223,1180,1181],{"class":228}," not",[223,1183,1184],{"class":268}," None\n",[223,1186,1188],{"class":100,"line":1187},21,[223,1189,271],{"class":232},[223,1191,1193,1196,1198,1200,1203,1206,1209,1212,1215,1218,1221,1223,1226],{"class":100,"line":1192},22,[223,1194,1195],{"class":268},"print",[223,1197,318],{"class":232},[223,1199,654],{"class":228},[223,1201,1202],{"class":261},"\"mean absolute error: ",[223,1204,1205],{"class":268},"{sum",[223,1207,1208],{"class":232},"(errors) ",[223,1210,1211],{"class":228},"\u002F",[223,1213,1214],{"class":268}," len",[223,1216,1217],{"class":232},"(errors)",[223,1219,1220],{"class":228},":.1f",[223,1222,351],{"class":268},[223,1224,1225],{"class":261}," mm\"",[223,1227,288],{"class":232},[14,1229,1230,1232,1233,1236,1237,1240,1241,1244],{},[191,1231,369],{}," Strictly the held-back points should be excluded from the interpolation before predicting them; interpolating from the full set and then testing on a subset of it flatters the result badly, because IDW passes almost exactly through each observation. Run the interpolation on the complement of the holdout for an honest figure. ",[220,1234,1235],{},"native:rastersampling"," writes one column per band with the given prefix, so a single-band raster gives ",[220,1238,1239],{},"pred_1",". Guarding against ",[220,1242,1243],{},"None"," handles held-back points that fall outside the mask.",[180,1246,1248],{"id":1247},"qgis-version-compatibility","QGIS version compatibility",[14,1250,1251,1252,1254],{},"The examples target ",[191,1253,193],{}," (Python 3.12).",[1256,1257,1258,1274],"table",{},[1259,1260,1261],"thead",{},[1262,1263,1264,1268,1271],"tr",{},[1265,1266,1267],"th",{},"QGIS version",[1265,1269,1270],{},"Python",[1265,1272,1273],{},"Notes",[1275,1276,1277,1295,1308,1321,1332],"tbody",{},[1262,1278,1279,1283,1286],{},[1280,1281,1282],"td",{},"3.16 LTR",[1280,1284,1285],{},"3.7",[1280,1287,1288,1291,1292,1294],{},[220,1289,1290],{},"qgis:idwinterpolation"," present with the ",[220,1293,373],{}," spec format.",[1262,1296,1297,1300,1303],{},[1280,1298,1299],{},"3.22 LTR",[1280,1301,1302],{},"3.9",[1280,1304,1305,1307],{},[220,1306,1235],{}," available for the validation step.",[1262,1309,1310,1313,1315],{},[1280,1311,1312],{},"3.28 LTR",[1280,1314,1302],{},[1280,1316,1317,1320],{},[220,1318,1319],{},"native:randomextract"," percentage method stable.",[1262,1322,1323,1326,1329],{},[1280,1324,1325],{},"3.34 LTR",[1280,1327,1328],{},"3.12",[1280,1330,1331],{},"Baseline for this page.",[1262,1333,1334,1337,1339],{},[1280,1335,1336],{},"3.40+",[1280,1338,1328],{},[1280,1340,1341],{},"Interpolation algorithms accept layer objects more tolerantly in the spec string.",[180,1343,1345],{"id":1344},"troubleshooting","Troubleshooting",[185,1347,1348,1357,1366,1372,1384,1390],{},[188,1349,1350,1353,1354,1356],{},[191,1351,1352],{},"The output raster is empty."," The attribute index in the spec is wrong, often ",[220,1355,381],{}," from a missing field. Print the index before building the string.",[188,1358,1359,1362,1363,1365],{},[191,1360,1361],{},"The surface is a set of bull's-eyes."," The power is too high. Drop ",[220,1364,529],{}," towards 2.",[188,1367,1368,1371],{},[191,1369,1370],{},"The surface is almost flat."," The power is too low, or the samples genuinely have little spatial structure. Compare against a power of 3.",[188,1373,1374,1377,1378,1380,1381,1383],{},[191,1375,1376],{},"It takes forever."," Every sample is used for every cell. Coarsen ",[220,1379,537],{},", or switch to ",[220,1382,544],{}," with a neighbour limit.",[188,1385,1386,1389],{},[191,1387,1388],{},"The surface is stretched east–west."," The layer is in a geographic CRS. Reproject the points first.",[188,1391,1392,1395],{},[191,1393,1394],{},"Values appear far from any sample."," That is IDW working as designed. Mask the output.",[180,1397,1399],{"id":1398},"conclusion","Conclusion",[14,1401,1402],{},"Build the spec carefully and check the attribute index, keep the power near 2 unless there is a reason, interpolate no finer than a quarter of the sample spacing, and always clip the result to a buffered mask so the map admits where it has no data. Then hold back a tenth of the samples and publish the error alongside the surface.",[180,1404,1406],{"id":1405},"frequently-asked-questions","Frequently Asked Questions",[14,1408,1409,1412,1413,1415,1416,1418],{},[191,1410,1411],{},"Can I limit IDW to a search radius?","\nNot in ",[220,1414,1290],{},", which uses every point. ",[220,1417,544],{}," takes a maximum number of neighbours and search radii, at the cost of a slightly different parameter vocabulary.",[14,1420,1421,1424,1425,1427,1428,1430,1431,1435],{},[191,1422,1423],{},"How do I interpolate the Z coordinate instead of an attribute?","\nSet the last field of the spec to ",[220,1426,1007],{}," and leave the attribute index at ",[220,1429,381],{},". That is the route for a layer of 3D spot heights or ",[21,1432,1434],{"href":1433},"\u002Fspatial-data-processing-automation\u002Fterrain-and-interpolation-analysis\u002Fcreate-contours-from-dem-pyqgis\u002F","contour vertices",".",[14,1437,1438,1441,1442,1446],{},[191,1439,1440],{},"Should I use IDW or TIN?","\nIDW when the phenomenon is smooth and the samples are dense; ",[21,1443,1445],{"href":1444},"\u002Fspatial-data-processing-automation\u002Fterrain-and-interpolation-analysis\u002Fbuild-tin-interpolation-pyqgis\u002F","TIN"," when the observations are trusted exactly and you want the surface to refuse to extrapolate.",[14,1448,1449,1452,1453,1457],{},[191,1450,1451],{},"Does IDW handle duplicate points?","\nTwo samples at the same coordinate give a zero distance and an infinite weight. QGIS handles it without crashing, but the result is one of the two values arbitrarily. Deduplicate first with ",[21,1454,1456],{"href":1455},"\u002Fspatial-data-processing-automation\u002Fattribute-tables-and-field-management\u002Fcount-features-by-attribute-pyqgis\u002F","count features by attribute"," or a spatial deduplication step.",[180,1459,1461],{"id":1460},"related","Related",[185,1463,1464,1469,1474,1480,1486],{},[188,1465,1466,1468],{},[21,1467,24],{"href":23}," — the guide this recipe belongs to",[188,1470,1471],{},[21,1472,1473],{"href":1444},"Build a TIN Interpolation in PyQGIS",[188,1475,1476],{},[21,1477,1479],{"href":1478},"\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fsample-raster-values-at-points-pyqgis\u002F","Sample Raster Values at Points in PyQGIS",[188,1481,1482],{},[21,1483,1485],{"href":1484},"\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fclip-raster-by-mask-layer-pyqgis\u002F","Clip a Raster by a Mask Layer in PyQGIS",[188,1487,1488],{},[21,1489,1491],{"href":1490},"\u002Fspatial-data-processing-automation\u002Fgeometry-operations-and-predicates\u002Fbuffer-geometry-pyqgis\u002F","Buffer a Geometry in PyQGIS",[1493,1494,1495],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}",{"title":218,"searchDepth":242,"depth":242,"links":1497},[1498,1499,1500,1501,1502,1503,1504,1505,1506,1507,1508],{"id":182,"depth":242,"text":183},{"id":207,"depth":242,"text":208},{"id":398,"depth":242,"text":399},{"id":548,"depth":242,"text":549},{"id":689,"depth":242,"text":690},{"id":958,"depth":242,"text":959},{"id":1247,"depth":242,"text":1248},{"id":1344,"depth":242,"text":1345},{"id":1398,"depth":242,"text":1399},{"id":1405,"depth":242,"text":1406},{"id":1460,"depth":242,"text":1461},"Build an inverse-distance-weighted surface from scattered samples with qgis:idwinterpolation — the layer spec string, choosing the power and pixel size, masking, and cross-validating the result.","md",{"slug":1512,"type":1513,"breadcrumb":1514,"datePublished":1515,"dateModified":1515},"idw-interpolation-from-points-pyqgis","article","IDW Interpolation","2026-08-27","\u002Fspatial-data-processing-automation\u002Fterrain-and-interpolation-analysis\u002Fidw-interpolation-from-points-pyqgis",{"title":5,"description":1509},"spatial-data-processing-automation\u002Fterrain-and-interpolation-analysis\u002Fidw-interpolation-from-points-pyqgis\u002Findex","q3-wehb13gRW_G1VPj91hZ29izdJkmB47d3IJFd_Moo",1787823363931]