[{"data":1,"prerenderedAt":1940},["ShallowReactive",2],{"doc:\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fresample-and-align-rasters-pyqgis":3},{"id":4,"title":5,"body":6,"description":1929,"extension":1930,"meta":1931,"navigation":192,"path":1936,"seo":1937,"stem":1938,"__hash__":1939},"docs\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fresample-and-align-rasters-pyqgis\u002Findex.md","Resample and Align Rasters in PyQGIS",{"type":7,"value":8,"toc":1914},"minimark",[9,13,17,26,136,141,151,155,158,421,428,432,435,491,494,498,504,819,828,832,835,903,1201,1210,1214,1217,1360,1370,1374,1377,1605,1614,1618,1621,1797,1802,1806,1811,1815,1841,1845,1848,1852,1858,1864,1870,1876,1880,1910],[10,11,5],"h1",{"id":12},"resample-and-align-rasters-in-pyqgis",[14,15,16],"p",{},"Raster arithmetic assumes that pixel (row, column) in one raster covers the same ground as pixel (row, column) in another. That holds only when both share a CRS, pixel size, origin and extent — a common grid. Real inputs rarely do: a 25 m DEM, a 10 m land-cover map, a 30 m satellite product and a 1 km climate grid, each with its own extent. Combining them correctly means choosing one target grid and bringing every input onto it, with a resampling method that respects what each raster's values mean.",[14,18,19,20,25],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002F","Raster Analysis Workflows",". It explains what a grid is, resamples rasters to a new resolution, aligns them to a reference raster, picks the right resampling method for continuous and categorical data, and verifies alignment before arithmetic.",[14,27,28],{},[29,30,35,39,43,50,59,69,75,81,85,90,94,97,100,105,109,112,115,121,126,129,132],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"xmlns":34},"0 0 760 280","img","A raster grid defined by CRS, pixel size, origin and extent, all of which must match for rasters to be aligned","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[36,37,38],"title",{},"Four properties define a grid",[40,41,42],"desc",{},"A raster grid is defined by its CRS, its pixel size in x and y, its origin which is the coordinate of the top-left corner, and its extent or number of rows and columns. Two rasters are aligned only when all four match. A half-pixel shift in origin, with everything else equal, already misaligns every pixel.",[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 CRS, size, origin, extent",[44,60],{"x":61,"y":62,"width":63,"height":64,"rx":65,"fill":66,"stroke":67,"style":68},"24","56","168","170","8","#eef7f4","#0f766e","stroke-width:2",[51,70,74],{"x":71,"y":72,"style":73,"fill":67,"textAnchor":57},"108","116.78","text-anchor:middle;font-size:11.5px;font-family:sans-serif;font-weight:bold","CRS",[51,76,80],{"x":71,"y":77,"style":78,"fill":79,"textAnchor":57},"144.78","text-anchor:middle;font-size:10.5px;font-family:sans-serif","#59645f","same system",[51,82,84],{"x":71,"y":83,"style":78,"fill":79,"textAnchor":57},"172.78","same units",[44,86],{"x":87,"y":62,"width":63,"height":64,"rx":65,"fill":88,"stroke":89,"style":68},"206","#eff3ff","#2563eb",[51,91,93],{"x":92,"y":72,"style":73,"fill":89,"textAnchor":57},"290","pixel size",[51,95,96],{"x":92,"y":77,"style":78,"fill":79,"textAnchor":57},"x and y",[51,98,99],{"x":92,"y":83,"style":78,"fill":79,"textAnchor":57},"e.g. 10 m",[44,101],{"x":102,"y":62,"width":63,"height":64,"rx":65,"fill":103,"stroke":104,"style":68},"388","#fdf2e2","#b45309",[51,106,108],{"x":107,"y":72,"style":73,"fill":104,"textAnchor":57},"472","origin",[51,110,111],{"x":107,"y":77,"style":78,"fill":79,"textAnchor":57},"top-left corner",[51,113,114],{"x":107,"y":83,"style":78,"fill":79,"textAnchor":57},"on the same lattice",[44,116],{"x":117,"y":62,"width":118,"height":64,"rx":65,"fill":119,"stroke":120,"style":68},"570","166","#e8efe6","#15803d",[51,122,125],{"x":123,"y":72,"style":73,"fill":124,"textAnchor":57},"653","#166534","extent",[51,127,128],{"x":123,"y":77,"style":78,"fill":79,"textAnchor":57},"rows × columns",[51,130,131],{"x":123,"y":83,"style":78,"fill":79,"textAnchor":57},"same window",[51,133,135],{"x":53,"y":134,"style":78,"fill":79,"textAnchor":57},"254","a half-pixel shift in origin misaligns every pixel",[137,138,140],"h2",{"id":139},"prerequisites","Prerequisites",[142,143,144,148],"ul",{},[145,146,147],"li",{},"QGIS 3.34 LTR or newer, or the QGIS 4 series, with the GDAL provider.",[145,149,150],{},"A reference raster that defines the target grid — usually the most important or finest input of the analysis.",[137,152,154],{"id":153},"inspect-the-grids-you-have","Inspect the grids you have",[14,156,157],{},"Before resampling anything, compare the grids. A small function reporting CRS, pixel size, origin and size makes the differences obvious.",[159,160,165],"pre",{"className":161,"code":162,"language":163,"meta":164,"style":164},"language-python shiki shiki-themes github-dark","from qgis.core import QgsRasterLayer\n\ndef grid(path):\n    r = QgsRasterLayer(path, \"g\")\n    e = r.extent()\n    return {\"crs\": r.crs().authid(),\n            \"px\": (round(r.rasterUnitsPerPixelX(), 6), round(r.rasterUnitsPerPixelY(), 6)),\n            \"origin\": (round(e.xMinimum(), 3), round(e.yMaximum(), 3)),\n            \"size\": (r.width(), r.height())}\n\ninputs = {\"dem\": \"\u002Fdata\u002Fmodel\u002Fdem_25m.tif\",\n          \"landcover\": \"\u002Fdata\u002Fmodel\u002Flandcover_10m.tif\",\n          \"rain\": \"\u002Fdata\u002Fmodel\u002Frain_1km.tif\"}\nfor name, path in inputs.items():\n    print(f\"{name:\u003C10}\", grid(path))\n","python","",[166,167,168,187,194,207,226,237,252,284,311,320,325,347,360,374,389],"code",{"__ignoreMap":164},[169,170,173,177,181,184],"span",{"class":171,"line":172},"line",1,[169,174,176],{"class":175},"snl16","from",[169,178,180],{"class":179},"s95oV"," qgis.core ",[169,182,183],{"class":175},"import",[169,185,186],{"class":179}," QgsRasterLayer\n",[169,188,190],{"class":171,"line":189},2,[169,191,193],{"emptyLinePlaceholder":192},true,"\n",[169,195,197,200,204],{"class":171,"line":196},3,[169,198,199],{"class":175},"def",[169,201,203],{"class":202},"svObZ"," grid",[169,205,206],{"class":179},"(path):\n",[169,208,210,213,216,219,223],{"class":171,"line":209},4,[169,211,212],{"class":179},"    r ",[169,214,215],{"class":175},"=",[169,217,218],{"class":179}," QgsRasterLayer(path, ",[169,220,222],{"class":221},"sU2Wk","\"g\"",[169,224,225],{"class":179},")\n",[169,227,229,232,234],{"class":171,"line":228},5,[169,230,231],{"class":179},"    e ",[169,233,215],{"class":175},[169,235,236],{"class":179}," r.extent()\n",[169,238,240,243,246,249],{"class":171,"line":239},6,[169,241,242],{"class":175},"    return",[169,244,245],{"class":179}," {",[169,247,248],{"class":221},"\"crs\"",[169,250,251],{"class":179},": r.crs().authid(),\n",[169,253,255,258,261,265,268,271,274,276,279,281],{"class":171,"line":254},7,[169,256,257],{"class":221},"            \"px\"",[169,259,260],{"class":179},": (",[169,262,264],{"class":263},"sDLfK","round",[169,266,267],{"class":179},"(r.rasterUnitsPerPixelX(), ",[169,269,270],{"class":263},"6",[169,272,273],{"class":179},"), ",[169,275,264],{"class":263},[169,277,278],{"class":179},"(r.rasterUnitsPerPixelY(), ",[169,280,270],{"class":263},[169,282,283],{"class":179},")),\n",[169,285,287,290,292,294,297,300,302,304,307,309],{"class":171,"line":286},8,[169,288,289],{"class":221},"            \"origin\"",[169,291,260],{"class":179},[169,293,264],{"class":263},[169,295,296],{"class":179},"(e.xMinimum(), ",[169,298,299],{"class":263},"3",[169,301,273],{"class":179},[169,303,264],{"class":263},[169,305,306],{"class":179},"(e.yMaximum(), ",[169,308,299],{"class":263},[169,310,283],{"class":179},[169,312,314,317],{"class":171,"line":313},9,[169,315,316],{"class":221},"            \"size\"",[169,318,319],{"class":179},": (r.width(), r.height())}\n",[169,321,323],{"class":171,"line":322},10,[169,324,193],{"emptyLinePlaceholder":192},[169,326,328,331,333,335,338,341,344],{"class":171,"line":327},11,[169,329,330],{"class":179},"inputs ",[169,332,215],{"class":175},[169,334,245],{"class":179},[169,336,337],{"class":221},"\"dem\"",[169,339,340],{"class":179},": ",[169,342,343],{"class":221},"\"\u002Fdata\u002Fmodel\u002Fdem_25m.tif\"",[169,345,346],{"class":179},",\n",[169,348,350,353,355,358],{"class":171,"line":349},12,[169,351,352],{"class":221},"          \"landcover\"",[169,354,340],{"class":179},[169,356,357],{"class":221},"\"\u002Fdata\u002Fmodel\u002Flandcover_10m.tif\"",[169,359,346],{"class":179},[169,361,363,366,368,371],{"class":171,"line":362},13,[169,364,365],{"class":221},"          \"rain\"",[169,367,340],{"class":179},[169,369,370],{"class":221},"\"\u002Fdata\u002Fmodel\u002Frain_1km.tif\"",[169,372,373],{"class":179},"}\n",[169,375,377,380,383,386],{"class":171,"line":376},14,[169,378,379],{"class":175},"for",[169,381,382],{"class":179}," name, path ",[169,384,385],{"class":175},"in",[169,387,388],{"class":179}," inputs.items():\n",[169,390,392,395,398,401,404,407,410,413,416,418],{"class":171,"line":391},15,[169,393,394],{"class":263},"    print",[169,396,397],{"class":179},"(",[169,399,400],{"class":175},"f",[169,402,403],{"class":221},"\"",[169,405,406],{"class":263},"{",[169,408,409],{"class":179},"name",[169,411,412],{"class":175},":\u003C10",[169,414,415],{"class":263},"}",[169,417,403],{"class":221},[169,419,420],{"class":179},", grid(path))\n",[14,422,423,427],{},[424,425,426],"strong",{},"Breakdown:"," Pixel size, origin and size together pin down every pixel's position. The origin uses the top-left corner (minimum x, maximum y), which is how GDAL stores the geotransform. Rounding hides floating-point noise in the printout without changing the comparison. Two rasters with the same CRS and pixel size but origins that differ by a non-multiple of the pixel size are misaligned even though they look identical at a glance.",[137,429,431],{"id":430},"choose-the-target-grid","Choose the target grid",[14,433,434],{},"The target grid is an analysis decision. The usual choices are the grid of the most important input, or a grid at the coarsest resolution among the inputs, which avoids inventing detail that coarse data does not contain.",[14,436,437],{},[29,438,441,444,447,450,453,457,462,467,471,475,478,482,485,488],{"viewBox":439,"role":32,"ariaLabel":440,"xmlns":34},"0 0 760 222","A choice between resampling to the finest grid, which invents apparent detail, and to the coarsest, which loses detail but stays honest",[36,442,443],{},"Finest or coarsest",[40,445,446],{},"Resampling coarse data to a fine grid multiplies pixels without adding information and inflates file sizes, while preserving the fine inputs. Resampling fine data to a coarse grid loses detail but keeps every input honest about its resolution. A common compromise is the resolution of the key input, with coarser inputs smoothed by bilinear interpolation.",[44,448],{"x":46,"y":46,"width":47,"height":449,"fill":49},"222",[51,451,452],{"x":53,"y":54,"style":55,"fill":56,"textAnchor":57},"Detail you have, or detail you invent?",[44,454],{"x":61,"y":62,"width":455,"height":456,"rx":65,"fill":103,"stroke":104,"style":68},"340","150",[51,458,461],{"x":459,"y":460,"style":73,"fill":104,"textAnchor":57},"194","92.78","finest grid (10 m)",[51,463,466],{"x":459,"y":464,"style":78,"fill":465,"textAnchor":57},"120.78","#2f3b35","keeps fine inputs",[51,468,470],{"x":459,"y":469,"style":78,"fill":465,"textAnchor":57},"148.78","1 km rain → 10,000× pixels",[51,472,474],{"x":459,"y":473,"style":78,"fill":79,"textAnchor":57},"176.78","detail is invented",[44,476],{"x":477,"y":62,"width":455,"height":456,"rx":65,"fill":119,"stroke":120,"style":68},"396",[51,479,481],{"x":480,"y":460,"style":73,"fill":124,"textAnchor":57},"566","coarsest grid (1 km)",[51,483,484],{"x":480,"y":464,"style":78,"fill":465,"textAnchor":57},"honest about resolution",[51,486,487],{"x":480,"y":469,"style":78,"fill":465,"textAnchor":57},"fine inputs aggregated",[51,489,490],{"x":480,"y":473,"style":78,"fill":79,"textAnchor":57},"detail is lost",[14,492,493],{},"There is no universally right answer: a land-cover analysis at parcel level needs the 10 m grid and must accept that rainfall is constant over each square kilometre; a regional climate model needs the 1 km grid and aggregates land cover into proportions. Decide based on the question and the resolution of the data that matters most, then bring everything onto that grid explicitly.",[137,495,497],{"id":496},"resample-to-the-reference-grid","Resample to the reference grid",[14,499,500,503],{},[166,501,502],{},"gdal:warpreproject"," reprojects and resamples in one step. Supplying the reference raster's CRS, pixel size and extent produces an output on exactly its grid.",[159,505,507],{"className":161,"code":506,"language":163,"meta":164,"style":164},"import processing\n\nref = QgsRasterLayer(inputs[\"dem\"], \"dem\")\ne = ref.extent()\ntarget_extent = f\"{e.xMinimum()},{e.xMaximum()},{e.yMinimum()},{e.yMaximum()} [{ref.crs().authid()}]\"\n\ndef align_to_reference(path, method, out):\n    return processing.run(\"gdal:warpreproject\", {\n        \"INPUT\": path,\n        \"SOURCE_CRS\": None,\n        \"TARGET_CRS\": ref.crs(),\n        \"RESAMPLING\": method,\n        \"NODATA\": None,\n        \"TARGET_RESOLUTION\": ref.rasterUnitsPerPixelX(),\n        \"TARGET_EXTENT\": target_extent,\n        \"TARGET_EXTENT_CRS\": ref.crs(),\n        \"MULTITHREADING\": True,\n        \"OPTIONS\": \"COMPRESS=DEFLATE|TILED=YES\",\n        \"OUTPUT\": out,\n    })[\"OUTPUT\"]\n\nrain_25 = align_to_reference(inputs[\"rain\"], 1, \"\u002Fdata\u002Fmodel\u002Frain_25m.tif\")          # bilinear\nlc_25 = align_to_reference(inputs[\"landcover\"], 6, \"\u002Fdata\u002Fmodel\u002Flandcover_25m.tif\")   # mode\n",[166,508,509,516,520,539,549,609,613,623,636,644,656,664,672,683,691,699,707,720,733,742,754,759,791],{"__ignoreMap":164},[169,510,511,513],{"class":171,"line":172},[169,512,183],{"class":175},[169,514,515],{"class":179}," processing\n",[169,517,518],{"class":171,"line":189},[169,519,193],{"emptyLinePlaceholder":192},[169,521,522,525,527,530,532,535,537],{"class":171,"line":196},[169,523,524],{"class":179},"ref ",[169,526,215],{"class":175},[169,528,529],{"class":179}," QgsRasterLayer(inputs[",[169,531,337],{"class":221},[169,533,534],{"class":179},"], ",[169,536,337],{"class":221},[169,538,225],{"class":179},[169,540,541,544,546],{"class":171,"line":209},[169,542,543],{"class":179},"e ",[169,545,215],{"class":175},[169,547,548],{"class":179}," ref.extent()\n",[169,550,551,554,556,559,561,563,566,568,571,573,576,578,580,582,585,587,589,591,594,596,599,601,604,606],{"class":171,"line":228},[169,552,553],{"class":179},"target_extent ",[169,555,215],{"class":175},[169,557,558],{"class":175}," f",[169,560,403],{"class":221},[169,562,406],{"class":263},[169,564,565],{"class":179},"e.xMinimum()",[169,567,415],{"class":263},[169,569,570],{"class":221},",",[169,572,406],{"class":263},[169,574,575],{"class":179},"e.xMaximum()",[169,577,415],{"class":263},[169,579,570],{"class":221},[169,581,406],{"class":263},[169,583,584],{"class":179},"e.yMinimum()",[169,586,415],{"class":263},[169,588,570],{"class":221},[169,590,406],{"class":263},[169,592,593],{"class":179},"e.yMaximum()",[169,595,415],{"class":263},[169,597,598],{"class":221}," [",[169,600,406],{"class":263},[169,602,603],{"class":179},"ref.crs().authid()",[169,605,415],{"class":263},[169,607,608],{"class":221},"]\"\n",[169,610,611],{"class":171,"line":239},[169,612,193],{"emptyLinePlaceholder":192},[169,614,615,617,620],{"class":171,"line":254},[169,616,199],{"class":175},[169,618,619],{"class":202}," align_to_reference",[169,621,622],{"class":179},"(path, method, out):\n",[169,624,625,627,630,633],{"class":171,"line":286},[169,626,242],{"class":175},[169,628,629],{"class":179}," processing.run(",[169,631,632],{"class":221},"\"gdal:warpreproject\"",[169,634,635],{"class":179},", {\n",[169,637,638,641],{"class":171,"line":313},[169,639,640],{"class":221},"        \"INPUT\"",[169,642,643],{"class":179},": path,\n",[169,645,646,649,651,654],{"class":171,"line":322},[169,647,648],{"class":221},"        \"SOURCE_CRS\"",[169,650,340],{"class":179},[169,652,653],{"class":263},"None",[169,655,346],{"class":179},[169,657,658,661],{"class":171,"line":327},[169,659,660],{"class":221},"        \"TARGET_CRS\"",[169,662,663],{"class":179},": ref.crs(),\n",[169,665,666,669],{"class":171,"line":349},[169,667,668],{"class":221},"        \"RESAMPLING\"",[169,670,671],{"class":179},": method,\n",[169,673,674,677,679,681],{"class":171,"line":362},[169,675,676],{"class":221},"        \"NODATA\"",[169,678,340],{"class":179},[169,680,653],{"class":263},[169,682,346],{"class":179},[169,684,685,688],{"class":171,"line":376},[169,686,687],{"class":221},"        \"TARGET_RESOLUTION\"",[169,689,690],{"class":179},": ref.rasterUnitsPerPixelX(),\n",[169,692,693,696],{"class":171,"line":391},[169,694,695],{"class":221},"        \"TARGET_EXTENT\"",[169,697,698],{"class":179},": target_extent,\n",[169,700,702,705],{"class":171,"line":701},16,[169,703,704],{"class":221},"        \"TARGET_EXTENT_CRS\"",[169,706,663],{"class":179},[169,708,710,713,715,718],{"class":171,"line":709},17,[169,711,712],{"class":221},"        \"MULTITHREADING\"",[169,714,340],{"class":179},[169,716,717],{"class":263},"True",[169,719,346],{"class":179},[169,721,723,726,728,731],{"class":171,"line":722},18,[169,724,725],{"class":221},"        \"OPTIONS\"",[169,727,340],{"class":179},[169,729,730],{"class":221},"\"COMPRESS=DEFLATE|TILED=YES\"",[169,732,346],{"class":179},[169,734,736,739],{"class":171,"line":735},19,[169,737,738],{"class":221},"        \"OUTPUT\"",[169,740,741],{"class":179},": out,\n",[169,743,745,748,751],{"class":171,"line":744},20,[169,746,747],{"class":179},"    })[",[169,749,750],{"class":221},"\"OUTPUT\"",[169,752,753],{"class":179},"]\n",[169,755,757],{"class":171,"line":756},21,[169,758,193],{"emptyLinePlaceholder":192},[169,760,762,765,767,770,773,775,778,781,784,787],{"class":171,"line":761},22,[169,763,764],{"class":179},"rain_25 ",[169,766,215],{"class":175},[169,768,769],{"class":179}," align_to_reference(inputs[",[169,771,772],{"class":221},"\"rain\"",[169,774,534],{"class":179},[169,776,777],{"class":263},"1",[169,779,780],{"class":179},", ",[169,782,783],{"class":221},"\"\u002Fdata\u002Fmodel\u002Frain_25m.tif\"",[169,785,786],{"class":179},")          ",[169,788,790],{"class":789},"sjoCn","# bilinear\n",[169,792,794,797,799,801,804,806,808,810,813,816],{"class":171,"line":793},23,[169,795,796],{"class":179},"lc_25 ",[169,798,215],{"class":175},[169,800,769],{"class":179},[169,802,803],{"class":221},"\"landcover\"",[169,805,534],{"class":179},[169,807,270],{"class":263},[169,809,780],{"class":179},[169,811,812],{"class":221},"\"\u002Fdata\u002Fmodel\u002Flandcover_25m.tif\"",[169,814,815],{"class":179},")   ",[169,817,818],{"class":789},"# mode\n",[14,820,821,823,824,827],{},[424,822,426],{}," Target CRS, resolution and extent together define the output grid; when the extent is the reference's own extent and the resolution its pixel size, the output's origin and size match the reference exactly. The resampling enum follows GDAL's order in the Processing dialog — 0 nearest, 1 bilinear, 2 cubic, 5 average, 6 mode on current releases; confirm with ",[166,825,826],{},"processing.algorithmHelp(\"gdal:warpreproject\")",". Rain is continuous, so bilinear interpolation gives smooth values; land cover is categorical, so mode picks the most common class among source pixels. Multithreading speeds up large warps.",[137,829,831],{"id":830},"pick-the-resampling-method-by-data-type","Pick the resampling method by data type",[14,833,834],{},"The single most common resampling error is using an interpolating method on categorical data, which produces class codes that do not exist.",[14,836,837],{},[29,838,841,844,847,850,853,855,860,864,868,872,875,878,881,884,887,890,894,897,900],{"viewBox":839,"role":32,"ariaLabel":840,"xmlns":34},"0 0 760 250","Resampling methods matched to data meaning: nearest or mode for categories, bilinear or average for continuous values, sum for counts",[36,842,843],{},"Method by data meaning",[40,845,846],{},"Categorical data such as land-cover codes must use nearest neighbour when the grid is similar or finer, and mode when aggregating to a coarser grid. Continuous data such as elevation or temperature uses bilinear or cubic when refining and average when aggregating. Counts and totals such as population per pixel must be summed when aggregating, not averaged.",[44,848],{"x":46,"y":46,"width":47,"height":849,"fill":49},"250",[51,851,852],{"x":53,"y":54,"style":55,"fill":56,"textAnchor":57},"What do the values mean?",[44,854],{"x":61,"y":62,"width":449,"height":64,"rx":65,"fill":66,"stroke":67,"style":68},[51,856,859],{"x":857,"y":858,"style":73,"fill":67,"textAnchor":57},"135","102.78","categorical",[51,861,863],{"x":857,"y":862,"style":78,"fill":465,"textAnchor":57},"130.78","land cover, soil class",[51,865,867],{"x":857,"y":866,"style":78,"fill":465,"textAnchor":57},"158.78","finer: nearest",[51,869,871],{"x":857,"y":870,"style":78,"fill":465,"textAnchor":57},"186.78","coarser: mode",[44,873],{"x":874,"y":62,"width":449,"height":64,"rx":65,"fill":88,"stroke":89,"style":68},"269",[51,876,877],{"x":53,"y":858,"style":73,"fill":89,"textAnchor":57},"continuous",[51,879,880],{"x":53,"y":862,"style":78,"fill":465,"textAnchor":57},"elevation, rainfall",[51,882,883],{"x":53,"y":866,"style":78,"fill":465,"textAnchor":57},"finer: bilinear\u002Fcubic",[51,885,886],{"x":53,"y":870,"style":78,"fill":465,"textAnchor":57},"coarser: average",[44,888],{"x":889,"y":62,"width":449,"height":64,"rx":65,"fill":103,"stroke":104,"style":68},"514",[51,891,893],{"x":892,"y":858,"style":73,"fill":104,"textAnchor":57},"625","counts",[51,895,896],{"x":892,"y":862,"style":78,"fill":465,"textAnchor":57},"population per pixel",[51,898,899],{"x":892,"y":866,"style":78,"fill":465,"textAnchor":57},"coarser: sum",[51,901,902],{"x":892,"y":870,"style":78,"fill":79,"textAnchor":57},"never average",[159,904,906],{"className":161,"code":905,"language":163,"meta":164,"style":164},"from qgis.core import QgsApplication\n\nalg = QgsApplication.processingRegistry().algorithmById(\"gdal:warpreproject\")\noptions = alg.parameterDefinition(\"RESAMPLING\").options()\nMETHOD = {}\nfor i, label in enumerate(options):\n    METHOD.setdefault(label.split()[0].lower(), i)      # \"Bilinear (2x2 kernel)\" → \"bilinear\"\nprint(METHOD)\n\ndef method_for(kind, refining):\n    if kind == \"categorical\":\n        return METHOD[\"nearest\"] if refining else METHOD[\"mode\"]\n    if kind == \"count\":\n        return METHOD[\"sum\"]\n    return METHOD[\"bilinear\"] if refining else METHOD[\"average\"]\n\nfor name, kind in ((\"rain\", \"continuous\"), (\"landcover\", \"categorical\")):\n    src = QgsRasterLayer(inputs[name], name)\n    refining = src.rasterUnitsPerPixelX() > ref.rasterUnitsPerPixelX()\n    print(name, \"→\", method_for(kind, refining))\n",[166,907,908,919,923,937,953,964,979,995,1006,1010,1020,1037,1072,1085,1098,1126,1130,1162,1172,1188],{"__ignoreMap":164},[169,909,910,912,914,916],{"class":171,"line":172},[169,911,176],{"class":175},[169,913,180],{"class":179},[169,915,183],{"class":175},[169,917,918],{"class":179}," QgsApplication\n",[169,920,921],{"class":171,"line":189},[169,922,193],{"emptyLinePlaceholder":192},[169,924,925,928,930,933,935],{"class":171,"line":196},[169,926,927],{"class":179},"alg ",[169,929,215],{"class":175},[169,931,932],{"class":179}," QgsApplication.processingRegistry().algorithmById(",[169,934,632],{"class":221},[169,936,225],{"class":179},[169,938,939,942,944,947,950],{"class":171,"line":209},[169,940,941],{"class":179},"options ",[169,943,215],{"class":175},[169,945,946],{"class":179}," alg.parameterDefinition(",[169,948,949],{"class":221},"\"RESAMPLING\"",[169,951,952],{"class":179},").options()\n",[169,954,955,958,961],{"class":171,"line":228},[169,956,957],{"class":263},"METHOD",[169,959,960],{"class":175}," =",[169,962,963],{"class":179}," {}\n",[169,965,966,968,971,973,976],{"class":171,"line":239},[169,967,379],{"class":175},[169,969,970],{"class":179}," i, label ",[169,972,385],{"class":175},[169,974,975],{"class":263}," enumerate",[169,977,978],{"class":179},"(options):\n",[169,980,981,984,987,989,992],{"class":171,"line":254},[169,982,983],{"class":263},"    METHOD",[169,985,986],{"class":179},".setdefault(label.split()[",[169,988,46],{"class":263},[169,990,991],{"class":179},"].lower(), i)      ",[169,993,994],{"class":789},"# \"Bilinear (2x2 kernel)\" → \"bilinear\"\n",[169,996,997,1000,1002,1004],{"class":171,"line":286},[169,998,999],{"class":263},"print",[169,1001,397],{"class":179},[169,1003,957],{"class":263},[169,1005,225],{"class":179},[169,1007,1008],{"class":171,"line":313},[169,1009,193],{"emptyLinePlaceholder":192},[169,1011,1012,1014,1017],{"class":171,"line":322},[169,1013,199],{"class":175},[169,1015,1016],{"class":202}," method_for",[169,1018,1019],{"class":179},"(kind, refining):\n",[169,1021,1022,1025,1028,1031,1034],{"class":171,"line":327},[169,1023,1024],{"class":175},"    if",[169,1026,1027],{"class":179}," kind ",[169,1029,1030],{"class":175},"==",[169,1032,1033],{"class":221}," \"categorical\"",[169,1035,1036],{"class":179},":\n",[169,1038,1039,1042,1045,1048,1051,1054,1057,1060,1063,1065,1067,1070],{"class":171,"line":349},[169,1040,1041],{"class":175},"        return",[169,1043,1044],{"class":263}," METHOD",[169,1046,1047],{"class":179},"[",[169,1049,1050],{"class":221},"\"nearest\"",[169,1052,1053],{"class":179},"] ",[169,1055,1056],{"class":175},"if",[169,1058,1059],{"class":179}," refining ",[169,1061,1062],{"class":175},"else",[169,1064,1044],{"class":263},[169,1066,1047],{"class":179},[169,1068,1069],{"class":221},"\"mode\"",[169,1071,753],{"class":179},[169,1073,1074,1076,1078,1080,1083],{"class":171,"line":362},[169,1075,1024],{"class":175},[169,1077,1027],{"class":179},[169,1079,1030],{"class":175},[169,1081,1082],{"class":221}," \"count\"",[169,1084,1036],{"class":179},[169,1086,1087,1089,1091,1093,1096],{"class":171,"line":376},[169,1088,1041],{"class":175},[169,1090,1044],{"class":263},[169,1092,1047],{"class":179},[169,1094,1095],{"class":221},"\"sum\"",[169,1097,753],{"class":179},[169,1099,1100,1102,1104,1106,1109,1111,1113,1115,1117,1119,1121,1124],{"class":171,"line":391},[169,1101,242],{"class":175},[169,1103,1044],{"class":263},[169,1105,1047],{"class":179},[169,1107,1108],{"class":221},"\"bilinear\"",[169,1110,1053],{"class":179},[169,1112,1056],{"class":175},[169,1114,1059],{"class":179},[169,1116,1062],{"class":175},[169,1118,1044],{"class":263},[169,1120,1047],{"class":179},[169,1122,1123],{"class":221},"\"average\"",[169,1125,753],{"class":179},[169,1127,1128],{"class":171,"line":701},[169,1129,193],{"emptyLinePlaceholder":192},[169,1131,1132,1134,1137,1139,1142,1144,1146,1149,1152,1154,1156,1159],{"class":171,"line":709},[169,1133,379],{"class":175},[169,1135,1136],{"class":179}," name, kind ",[169,1138,385],{"class":175},[169,1140,1141],{"class":179}," ((",[169,1143,772],{"class":221},[169,1145,780],{"class":179},[169,1147,1148],{"class":221},"\"continuous\"",[169,1150,1151],{"class":179},"), (",[169,1153,803],{"class":221},[169,1155,780],{"class":179},[169,1157,1158],{"class":221},"\"categorical\"",[169,1160,1161],{"class":179},")):\n",[169,1163,1164,1167,1169],{"class":171,"line":722},[169,1165,1166],{"class":179},"    src ",[169,1168,215],{"class":175},[169,1170,1171],{"class":179}," QgsRasterLayer(inputs[name], name)\n",[169,1173,1174,1177,1179,1182,1185],{"class":171,"line":735},[169,1175,1176],{"class":179},"    refining ",[169,1178,215],{"class":175},[169,1180,1181],{"class":179}," src.rasterUnitsPerPixelX() ",[169,1183,1184],{"class":175},">",[169,1186,1187],{"class":179}," ref.rasterUnitsPerPixelX()\n",[169,1189,1190,1192,1195,1198],{"class":171,"line":744},[169,1191,394],{"class":263},[169,1193,1194],{"class":179},"(name, ",[169,1196,1197],{"class":221},"\"→\"",[169,1199,1200],{"class":179},", method_for(kind, refining))\n",[14,1202,1203,1205,1206,1209],{},[424,1204,426],{}," Reading the method list from the algorithm's own parameter definition maps names to positions on whatever GDAL version is installed, so the script never depends on a hard-coded enum order. Encoding the rule in a function makes the choice explicit and reviewable rather than a magic number per call. \"Refining\" means going to smaller pixels. Counts are a special case: population per 1 km pixel aggregated to 5 km must be summed, or totals shrink by a factor of 25; refining counts properly needs redistribution, not resampling. The ",[166,1207,1208],{},"sum"," method requires GDAL 3.1 or newer; if it is missing from the printed list, the installed GDAL is too old for count aggregation in one step.",[137,1211,1213],{"id":1212},"verify-alignment-before-arithmetic","Verify alignment before arithmetic",[14,1215,1216],{},"A check that every aligned raster matches the reference grid exactly turns silent misalignment into an immediate error.",[159,1218,1220],{"className":161,"code":1219,"language":163,"meta":164,"style":164},"def assert_aligned(paths, reference):\n    g0 = grid(reference)\n    for p in paths:\n        g = grid(p)\n        if g != g0:\n            raise ValueError(f\"{p} not aligned:\\n  {g}\\n  vs reference {g0}\")\n    print(len(paths), \"rasters aligned with\", reference)\n\nassert_aligned([rain_25, lc_25], inputs[\"dem\"])\n",[166,1221,1222,1232,1242,1255,1265,1279,1328,1346,1350],{"__ignoreMap":164},[169,1223,1224,1226,1229],{"class":171,"line":172},[169,1225,199],{"class":175},[169,1227,1228],{"class":202}," assert_aligned",[169,1230,1231],{"class":179},"(paths, reference):\n",[169,1233,1234,1237,1239],{"class":171,"line":189},[169,1235,1236],{"class":179},"    g0 ",[169,1238,215],{"class":175},[169,1240,1241],{"class":179}," grid(reference)\n",[169,1243,1244,1247,1250,1252],{"class":171,"line":196},[169,1245,1246],{"class":175},"    for",[169,1248,1249],{"class":179}," p ",[169,1251,385],{"class":175},[169,1253,1254],{"class":179}," paths:\n",[169,1256,1257,1260,1262],{"class":171,"line":209},[169,1258,1259],{"class":179},"        g ",[169,1261,215],{"class":175},[169,1263,1264],{"class":179}," grid(p)\n",[169,1266,1267,1270,1273,1276],{"class":171,"line":228},[169,1268,1269],{"class":175},"        if",[169,1271,1272],{"class":179}," g ",[169,1274,1275],{"class":175},"!=",[169,1277,1278],{"class":179}," g0:\n",[169,1280,1281,1284,1287,1289,1291,1293,1295,1297,1299,1302,1305,1308,1311,1314,1317,1319,1322,1324,1326],{"class":171,"line":239},[169,1282,1283],{"class":175},"            raise",[169,1285,1286],{"class":263}," ValueError",[169,1288,397],{"class":179},[169,1290,400],{"class":175},[169,1292,403],{"class":221},[169,1294,406],{"class":263},[169,1296,14],{"class":179},[169,1298,415],{"class":263},[169,1300,1301],{"class":221}," not aligned:",[169,1303,1304],{"class":263},"\\n",[169,1306,1307],{"class":263},"  {",[169,1309,1310],{"class":179},"g",[169,1312,1313],{"class":263},"}\\n",[169,1315,1316],{"class":221},"  vs reference ",[169,1318,406],{"class":263},[169,1320,1321],{"class":179},"g0",[169,1323,415],{"class":263},[169,1325,403],{"class":221},[169,1327,225],{"class":179},[169,1329,1330,1332,1334,1337,1340,1343],{"class":171,"line":254},[169,1331,394],{"class":263},[169,1333,397],{"class":179},[169,1335,1336],{"class":263},"len",[169,1338,1339],{"class":179},"(paths), ",[169,1341,1342],{"class":221},"\"rasters aligned with\"",[169,1344,1345],{"class":179},", reference)\n",[169,1347,1348],{"class":171,"line":286},[169,1349,193],{"emptyLinePlaceholder":192},[169,1351,1352,1355,1357],{"class":171,"line":313},[169,1353,1354],{"class":179},"assert_aligned([rain_25, lc_25], inputs[",[169,1356,337],{"class":221},[169,1358,1359],{"class":179},"])\n",[14,1361,1362,1364,1365,1369],{},[424,1363,426],{}," Comparing the full grid description — CRS, pixel size, origin, size — catches every kind of mismatch, including the half-pixel origin shift that is invisible on the map. Run it at the start of any script that combines rasters; the ",[21,1366,1368],{"href":1367},"\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fraster-calculator-pyqgis\u002F","raster calculator"," will otherwise happily resample on the fly or produce results offset by half a pixel.",[137,1371,1373],{"id":1372},"snap-an-extent-to-the-grid","Snap an extent to the grid",[14,1375,1376],{},"When clipping or creating new rasters for a sub-area, the extent must also fall on the grid lattice, or the new raster's origin drifts. Snapping the extent outward to whole pixels keeps it aligned.",[159,1378,1380],{"className":161,"code":1379,"language":163,"meta":164,"style":164},"import math\nfrom qgis.core import QgsRectangle\n\ndef snap_extent(rect, reference):\n    px = reference.rasterUnitsPerPixelX()\n    py = reference.rasterUnitsPerPixelY()\n    ox, oy = reference.extent().xMinimum(), reference.extent().yMaximum()\n    xmin = ox + math.floor((rect.xMinimum() - ox) \u002F px) * px\n    xmax = ox + math.ceil((rect.xMaximum() - ox) \u002F px) * px\n    ymax = oy - math.floor((oy - rect.yMaximum()) \u002F py) * py\n    ymin = oy - math.ceil((oy - rect.yMinimum()) \u002F py) * py\n    return QgsRectangle(xmin, ymin, xmax, ymax)\n\narea = QgsProject.instance().mapLayersByName(\"catchment\")[0].extent()\nprint(snap_extent(area, ref).toString(3))\n",[166,1381,1382,1389,1400,1404,1414,1424,1434,1444,1478,1504,1534,1561,1568,1572,1593],{"__ignoreMap":164},[169,1383,1384,1386],{"class":171,"line":172},[169,1385,183],{"class":175},[169,1387,1388],{"class":179}," math\n",[169,1390,1391,1393,1395,1397],{"class":171,"line":189},[169,1392,176],{"class":175},[169,1394,180],{"class":179},[169,1396,183],{"class":175},[169,1398,1399],{"class":179}," QgsRectangle\n",[169,1401,1402],{"class":171,"line":196},[169,1403,193],{"emptyLinePlaceholder":192},[169,1405,1406,1408,1411],{"class":171,"line":209},[169,1407,199],{"class":175},[169,1409,1410],{"class":202}," snap_extent",[169,1412,1413],{"class":179},"(rect, reference):\n",[169,1415,1416,1419,1421],{"class":171,"line":228},[169,1417,1418],{"class":179},"    px ",[169,1420,215],{"class":175},[169,1422,1423],{"class":179}," reference.rasterUnitsPerPixelX()\n",[169,1425,1426,1429,1431],{"class":171,"line":239},[169,1427,1428],{"class":179},"    py ",[169,1430,215],{"class":175},[169,1432,1433],{"class":179}," reference.rasterUnitsPerPixelY()\n",[169,1435,1436,1439,1441],{"class":171,"line":254},[169,1437,1438],{"class":179},"    ox, oy ",[169,1440,215],{"class":175},[169,1442,1443],{"class":179}," reference.extent().xMinimum(), reference.extent().yMaximum()\n",[169,1445,1446,1449,1451,1454,1457,1460,1463,1466,1469,1472,1475],{"class":171,"line":286},[169,1447,1448],{"class":179},"    xmin ",[169,1450,215],{"class":175},[169,1452,1453],{"class":179}," ox ",[169,1455,1456],{"class":175},"+",[169,1458,1459],{"class":179}," math.floor((rect.xMinimum() ",[169,1461,1462],{"class":175},"-",[169,1464,1465],{"class":179}," ox) ",[169,1467,1468],{"class":175},"\u002F",[169,1470,1471],{"class":179}," px) ",[169,1473,1474],{"class":175},"*",[169,1476,1477],{"class":179}," px\n",[169,1479,1480,1483,1485,1487,1489,1492,1494,1496,1498,1500,1502],{"class":171,"line":313},[169,1481,1482],{"class":179},"    xmax ",[169,1484,215],{"class":175},[169,1486,1453],{"class":179},[169,1488,1456],{"class":175},[169,1490,1491],{"class":179}," math.ceil((rect.xMaximum() ",[169,1493,1462],{"class":175},[169,1495,1465],{"class":179},[169,1497,1468],{"class":175},[169,1499,1471],{"class":179},[169,1501,1474],{"class":175},[169,1503,1477],{"class":179},[169,1505,1506,1509,1511,1514,1516,1519,1521,1524,1526,1529,1531],{"class":171,"line":322},[169,1507,1508],{"class":179},"    ymax ",[169,1510,215],{"class":175},[169,1512,1513],{"class":179}," oy ",[169,1515,1462],{"class":175},[169,1517,1518],{"class":179}," math.floor((oy ",[169,1520,1462],{"class":175},[169,1522,1523],{"class":179}," rect.yMaximum()) ",[169,1525,1468],{"class":175},[169,1527,1528],{"class":179}," py) ",[169,1530,1474],{"class":175},[169,1532,1533],{"class":179}," py\n",[169,1535,1536,1539,1541,1543,1545,1548,1550,1553,1555,1557,1559],{"class":171,"line":327},[169,1537,1538],{"class":179},"    ymin ",[169,1540,215],{"class":175},[169,1542,1513],{"class":179},[169,1544,1462],{"class":175},[169,1546,1547],{"class":179}," math.ceil((oy ",[169,1549,1462],{"class":175},[169,1551,1552],{"class":179}," rect.yMinimum()) ",[169,1554,1468],{"class":175},[169,1556,1528],{"class":179},[169,1558,1474],{"class":175},[169,1560,1533],{"class":179},[169,1562,1563,1565],{"class":171,"line":349},[169,1564,242],{"class":175},[169,1566,1567],{"class":179}," QgsRectangle(xmin, ymin, xmax, ymax)\n",[169,1569,1570],{"class":171,"line":362},[169,1571,193],{"emptyLinePlaceholder":192},[169,1573,1574,1577,1579,1582,1585,1588,1590],{"class":171,"line":376},[169,1575,1576],{"class":179},"area ",[169,1578,215],{"class":175},[169,1580,1581],{"class":179}," QgsProject.instance().mapLayersByName(",[169,1583,1584],{"class":221},"\"catchment\"",[169,1586,1587],{"class":179},")[",[169,1589,46],{"class":263},[169,1591,1592],{"class":179},"].extent()\n",[169,1594,1595,1597,1600,1602],{"class":171,"line":391},[169,1596,999],{"class":263},[169,1598,1599],{"class":179},"(snap_extent(area, ref).toString(",[169,1601,299],{"class":263},[169,1603,1604],{"class":179},"))\n",[14,1606,1607,1609,1610,1613],{},[424,1608,426],{}," Each edge is moved outward to the nearest pixel boundary of the reference lattice, measured from the reference origin, so the snapped rectangle contains the area and starts and ends exactly on pixel edges. Using it as the target extent for clips and warps of a sub-area produces rasters that align with the full reference and with each other. Add ",[166,1611,1612],{},"from qgis.core import QgsProject"," if you run this alone.",[137,1615,1617],{"id":1616},"aggregate-categories-into-proportions","Aggregate categories into proportions",[14,1619,1620],{},"When a fine categorical raster is aggregated to a coarse grid, mode keeps only the dominant class and throws away the mix. For many models the mix is exactly what matters: the share of forest in each 1 km cell, not whether forest is the most common class. Computing one proportion raster per class preserves it.",[159,1622,1624],{"className":161,"code":1623,"language":163,"meta":164,"style":164},"forest_mask = processing.run(\"gdal:rastercalculator\", {\n    \"INPUT_A\": inputs[\"landcover\"], \"BAND_A\": 1,\n    \"FORMULA\": \"(A == 311) + (A == 312) + (A == 313)\",\n    \"RTYPE\": 5, \"NO_DATA\": -9999,\n    \"OUTPUT\": \"\u002Fdata\u002Fmodel\u002Fforest_10m.tif\"})[\"OUTPUT\"]\n\nforest_share = processing.run(\"gdal:warpreproject\", {\n    \"INPUT\": forest_mask, \"TARGET_CRS\": ref.crs(),\n    \"RESAMPLING\": METHOD[\"average\"], \"TARGET_RESOLUTION\": 1000,\n    \"TARGET_EXTENT\": target_extent, \"TARGET_EXTENT_CRS\": ref.crs(),\n    \"OUTPUT\": \"\u002Fdata\u002Fmodel\u002Fforest_share_1km.tif\"})[\"OUTPUT\"]\n",[166,1625,1626,1640,1661,1673,1697,1714,1718,1731,1744,1769,1782],{"__ignoreMap":164},[169,1627,1628,1631,1633,1635,1638],{"class":171,"line":172},[169,1629,1630],{"class":179},"forest_mask ",[169,1632,215],{"class":175},[169,1634,629],{"class":179},[169,1636,1637],{"class":221},"\"gdal:rastercalculator\"",[169,1639,635],{"class":179},[169,1641,1642,1645,1648,1650,1652,1655,1657,1659],{"class":171,"line":189},[169,1643,1644],{"class":221},"    \"INPUT_A\"",[169,1646,1647],{"class":179},": inputs[",[169,1649,803],{"class":221},[169,1651,534],{"class":179},[169,1653,1654],{"class":221},"\"BAND_A\"",[169,1656,340],{"class":179},[169,1658,777],{"class":263},[169,1660,346],{"class":179},[169,1662,1663,1666,1668,1671],{"class":171,"line":196},[169,1664,1665],{"class":221},"    \"FORMULA\"",[169,1667,340],{"class":179},[169,1669,1670],{"class":221},"\"(A == 311) + (A == 312) + (A == 313)\"",[169,1672,346],{"class":179},[169,1674,1675,1678,1680,1683,1685,1688,1690,1692,1695],{"class":171,"line":209},[169,1676,1677],{"class":221},"    \"RTYPE\"",[169,1679,340],{"class":179},[169,1681,1682],{"class":263},"5",[169,1684,780],{"class":179},[169,1686,1687],{"class":221},"\"NO_DATA\"",[169,1689,340],{"class":179},[169,1691,1462],{"class":175},[169,1693,1694],{"class":263},"9999",[169,1696,346],{"class":179},[169,1698,1699,1702,1704,1707,1710,1712],{"class":171,"line":228},[169,1700,1701],{"class":221},"    \"OUTPUT\"",[169,1703,340],{"class":179},[169,1705,1706],{"class":221},"\"\u002Fdata\u002Fmodel\u002Fforest_10m.tif\"",[169,1708,1709],{"class":179},"})[",[169,1711,750],{"class":221},[169,1713,753],{"class":179},[169,1715,1716],{"class":171,"line":239},[169,1717,193],{"emptyLinePlaceholder":192},[169,1719,1720,1723,1725,1727,1729],{"class":171,"line":254},[169,1721,1722],{"class":179},"forest_share ",[169,1724,215],{"class":175},[169,1726,629],{"class":179},[169,1728,632],{"class":221},[169,1730,635],{"class":179},[169,1732,1733,1736,1739,1742],{"class":171,"line":286},[169,1734,1735],{"class":221},"    \"INPUT\"",[169,1737,1738],{"class":179},": forest_mask, ",[169,1740,1741],{"class":221},"\"TARGET_CRS\"",[169,1743,663],{"class":179},[169,1745,1746,1749,1751,1753,1755,1757,1759,1762,1764,1767],{"class":171,"line":313},[169,1747,1748],{"class":221},"    \"RESAMPLING\"",[169,1750,340],{"class":179},[169,1752,957],{"class":263},[169,1754,1047],{"class":179},[169,1756,1123],{"class":221},[169,1758,534],{"class":179},[169,1760,1761],{"class":221},"\"TARGET_RESOLUTION\"",[169,1763,340],{"class":179},[169,1765,1766],{"class":263},"1000",[169,1768,346],{"class":179},[169,1770,1771,1774,1777,1780],{"class":171,"line":322},[169,1772,1773],{"class":221},"    \"TARGET_EXTENT\"",[169,1775,1776],{"class":179},": target_extent, ",[169,1778,1779],{"class":221},"\"TARGET_EXTENT_CRS\"",[169,1781,663],{"class":179},[169,1783,1784,1786,1788,1791,1793,1795],{"class":171,"line":327},[169,1785,1701],{"class":221},[169,1787,340],{"class":179},[169,1789,1790],{"class":221},"\"\u002Fdata\u002Fmodel\u002Fforest_share_1km.tif\"",[169,1792,1709],{"class":179},[169,1794,750],{"class":221},[169,1796,753],{"class":179},[14,1798,1799,1801],{},[424,1800,426],{}," The calculator turns the categorical raster into a 0\u002F1 mask for the forest classes, as floating point so averages are fractional. Averaging that mask onto the 1 km grid gives, in each coarse cell, the fraction of fine pixels that were forest — a value between 0 and 1 that carries far more information than a single dominant class. Repeat per class of interest; the shares across all classes sum to one wherever the fine raster has data.",[137,1803,1805],{"id":1804},"qgis-version-compatibility","QGIS version compatibility",[14,1807,1808,1810],{},[166,1809,502],{}," with target extent, resolution and multithreading works on QGIS 3.34 LTR, 3.40 LTR and QGIS 4. The resampling enum list depends on the bundled GDAL version; current releases include sum, min, max, median and quartile methods. Always verify enum positions on the version that will run the script.",[137,1812,1814],{"id":1813},"troubleshooting","Troubleshooting",[142,1816,1817,1823,1829,1835],{},[145,1818,1819,1822],{},[424,1820,1821],{},"Land cover has codes that do not exist."," Bilinear or cubic was used on categorical data; use nearest or mode.",[145,1824,1825,1828],{},[424,1826,1827],{},"Population totals shrank."," Counts were averaged when aggregating; use sum.",[145,1830,1831,1834],{},[424,1832,1833],{},"Results are offset by half a pixel."," Origins differ; align with an explicit target extent.",[145,1836,1837,1840],{},[424,1838,1839],{},"The output is much larger than expected."," A coarse raster was resampled to a much finer grid; reconsider the target resolution.",[137,1842,1844],{"id":1843},"conclusion","Conclusion",[14,1846,1847],{},"Inspect each input's CRS, pixel size, origin and extent, choose a target grid that fits the question, warp every input onto it with explicit resolution and extent, choose nearest or mode for categories, bilinear or average for continuous values and sum for counts, snap sub-area extents to the lattice, and assert alignment before any arithmetic.",[137,1849,1851],{"id":1850},"frequently-asked-questions","Frequently Asked Questions",[14,1853,1854,1857],{},[424,1855,1856],{},"Does the QGIS raster calculator align automatically?","\nIt resamples inputs to the output grid on the fly using nearest neighbour, which is wrong for continuous data. Align explicitly first.",[14,1859,1860,1863],{},[424,1861,1862],{},"Can I align without reprojecting?","\nYes — the same call with the input's own CRS only resamples and shifts to the target lattice.",[14,1865,1866,1869],{},[424,1867,1868],{},"Is cubic better than bilinear?","\nSmoother, but it can overshoot near sharp edges. Bilinear is the safer default for most continuous data.",[14,1871,1872,1875],{},[424,1873,1874],{},"How do I align many rasters at once?","\nLoop over them with the same reference, as above, or build a VRT on the target grid.",[137,1877,1879],{"id":1878},"related","Related",[142,1881,1882,1887,1893,1899,1905],{},[145,1883,1884,1886],{},[21,1885,24],{"href":23}," — the guide this recipe belongs to",[145,1888,1889],{},[21,1890,1892],{"href":1891},"\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Frasterize-vector-layer-pyqgis\u002F","Rasterize a Vector Layer in PyQGIS",[145,1894,1895],{},[21,1896,1898],{"href":1897},"\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fmerge-raster-tiles-into-mosaic-pyqgis\u002F","Merge Raster Tiles into a Mosaic in PyQGIS",[145,1900,1901],{},[21,1902,1904],{"href":1903},"\u002Fspatial-data-processing-automation\u002Fcoordinate-reference-systems\u002Fbatch-reprojecting-raster-datasets\u002F","Batch Reprojecting Raster Datasets",[145,1906,1907],{},[21,1908,1909],{"href":1367},"Use the Raster Calculator in PyQGIS",[1911,1912,1913],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .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":164,"searchDepth":189,"depth":189,"links":1915},[1916,1917,1918,1919,1920,1921,1922,1923,1924,1925,1926,1927,1928],{"id":139,"depth":189,"text":140},{"id":153,"depth":189,"text":154},{"id":430,"depth":189,"text":431},{"id":496,"depth":189,"text":497},{"id":830,"depth":189,"text":831},{"id":1212,"depth":189,"text":1213},{"id":1372,"depth":189,"text":1373},{"id":1616,"depth":189,"text":1617},{"id":1804,"depth":189,"text":1805},{"id":1813,"depth":189,"text":1814},{"id":1843,"depth":189,"text":1844},{"id":1850,"depth":189,"text":1851},{"id":1878,"depth":189,"text":1879},"Put rasters on a common grid before combining them — resampling to a new pixel size with gdal:warpreproject, choosing nearest, bilinear, average or mode by data type, aligning extents and origins to a reference raster, and checking alignment before any raster arithmetic.","md",{"slug":1932,"type":1933,"breadcrumb":1934,"datePublished":1935,"dateModified":1935},"resample-and-align-rasters-pyqgis","article","Resample and Align Rasters","2026-10-02","\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fresample-and-align-rasters-pyqgis",{"title":5,"description":1929},"spatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fresample-and-align-rasters-pyqgis\u002Findex","MOM4jZH7U9WXuRRaIbjhfUkEopSJoshf9w6RWV17g94",1790966264776]