[{"data":1,"prerenderedAt":1866},["ShallowReactive",2],{"doc:\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fmerge-raster-tiles-into-mosaic-pyqgis":3},{"id":4,"title":5,"body":6,"description":1855,"extension":1856,"meta":1857,"navigation":249,"path":1862,"seo":1863,"stem":1864,"__hash__":1865},"docs\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fmerge-raster-tiles-into-mosaic-pyqgis\u002Findex.md","Merge Raster Tiles into a Mosaic in PyQGIS",{"type":7,"value":8,"toc":1840},"minimark",[9,13,17,31,163,168,184,188,191,550,567,571,576,799,820,824,830,895,1091,1096,1100,1103,1293,1303,1307,1310,1452,1457,1461,1464,1519,1527,1531,1534,1686,1700,1704,1713,1717,1752,1756,1759,1763,1776,1782,1792,1805,1809,1836],[10,11,5],"h1",{"id":12},"merge-raster-tiles-into-a-mosaic-in-pyqgis",[14,15,16],"p",{},"Raster data rarely arrives as one file. National DEMs come as hundreds of 1 km or 10 km tiles, orthophotos as map sheets, satellite imagery as scenes cut along orbit paths. Analyses — slope, viewshed, zonal statistics across a catchment that spans six tiles — need a single continuous raster. Merging is simple in principle and full of small traps in practice: tiles in different CRSs, slightly different resolutions, inconsistent NoData values, overlaps where two tiles disagree.",[14,18,19,20,25,26,30],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002F","Raster Analysis Workflows",". It inventories a folder of tiles, checks they are compatible, merges them with ",[27,28,29],"code",{},"gdal:merge"," into a compressed tiled GeoTIFF, handles NoData and overlaps deliberately, and compares the result with a virtual raster that merges nothing at all.",[14,32,33],{},[34,35,40,44,48,55,72,81,90,96,101,105,112,118,122,125,128,132,140,146,150,155,159],"svg",{"viewBox":36,"role":37,"ariaLabel":38,"xmlns":39},"0 0 760 234","img","Tiles inventoried for compatibility, fixed where needed, then merged into one compressed GeoTIFF or referenced by a virtual raster","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[41,42,43],"title",{},"From tiles to one raster",[45,46,47],"desc",{},"A folder of tiles is first inventoried: CRS, pixel size, data type, band count and NoData for each. Incompatible tiles are reprojected or resampled. The compatible set is merged with gdal:merge into one GeoTIFF, with NoData declared so gaps between tiles stay transparent, and compression and tiling applied. Alternatively, a VRT references the tiles without copying them.",[49,50],"rect",{"x":51,"y":51,"width":52,"height":53,"fill":54},"0","760","234","#f6f3ea",[56,57,58],"defs",{},[59,60,67],"marker",{"id":61,"viewBox":62,"refX":63,"refY":64,"markerWidth":65,"markerHeight":65,"orient":66},"mosFlowArrow","0 0 10 10","8","5","7","auto-start-reverse",[68,69],"path",{"d":70,"fill":71},"M0 0 L10 5 L0 10 z","#2f3b35",[73,74,80],"text",{"x":75,"y":76,"style":77,"fill":78,"textAnchor":79},"380","28","text-anchor:middle;font-size:14px;font-family:sans-serif;font-weight:bold","#17211d","middle","Inventory, align, merge",[49,82],{"x":83,"y":84,"width":85,"height":86,"rx":63,"fill":87,"stroke":88,"style":89},"24","60","160","150","#fffdf7","#59645f","stroke-width:2",[73,91,95],{"x":92,"y":93,"style":94,"fill":78,"textAnchor":79},"104","112.78","text-anchor:middle;font-size:11.5px;font-family:sans-serif;font-weight:bold","tiles",[73,97,100],{"x":92,"y":98,"style":99,"fill":88,"textAnchor":79},"138.78","text-anchor:middle;font-size:10.5px;font-family:sans-serif","dem_*.tif",[73,102,104],{"x":92,"y":103,"style":99,"fill":88,"textAnchor":79},"164.78","hundreds",[106,107],"line",{"x1":108,"y1":109,"x2":110,"y2":109,"stroke":71,"style":111},"184","135","224","stroke-width:1.8;marker-end:url(#mosFlowArrow)",[49,113],{"x":114,"y":84,"width":115,"height":86,"rx":63,"fill":116,"stroke":117,"style":89},"228","180","#fdf2e2","#b45309",[73,119,121],{"x":120,"y":93,"style":94,"fill":117,"textAnchor":79},"318","inventory",[73,123,124],{"x":120,"y":98,"style":99,"fill":71,"textAnchor":79},"CRS · pixel size",[73,126,127],{"x":120,"y":103,"style":99,"fill":71,"textAnchor":79},"dtype · NoData",[106,129],{"x1":130,"y1":109,"x2":131,"y2":109,"stroke":71,"style":111},"408","448",[49,133],{"x":134,"y":135,"width":136,"height":137,"rx":63,"fill":138,"stroke":139,"style":89},"452","52","284","72","#e8efe6","#15803d",[73,141,145],{"x":142,"y":143,"style":94,"fill":144,"textAnchor":79},"594","80.78","#166534","gdal:merge → GeoTIFF",[73,147,149],{"x":142,"y":148,"style":99,"fill":88,"textAnchor":79},"102.78","one file, compressed, tiled",[49,151],{"x":134,"y":152,"width":136,"height":137,"rx":63,"fill":153,"stroke":154,"style":89},"146","#eff3ff","#2563eb",[73,156,158],{"x":142,"y":157,"style":94,"fill":154,"textAnchor":79},"174.78","or: VRT",[73,160,162],{"x":142,"y":161,"style":99,"fill":88,"textAnchor":79},"196.78","references tiles, no copy",[164,165,167],"h2",{"id":166},"prerequisites","Prerequisites",[169,170,171,175,181],"ul",{},[172,173,174],"li",{},"QGIS 3.34 LTR or newer, or the QGIS 4 series, with the GDAL Processing provider (enabled by default).",[172,176,177,178,180],{},"A folder of raster tiles. The example uses 1 km DEM tiles named ",[27,179,100],{},".",[172,182,183],{},"Enough disk space for the output: a merged GeoTIFF is roughly the sum of the inputs, less with compression.",[164,185,187],{"id":186},"inventory-the-tiles","Inventory the tiles",[14,189,190],{},"Merging assumes every tile shares a CRS, pixel size, data type and band count. Checking that first takes seconds and prevents a merged raster that is subtly wrong.",[192,193,198],"pre",{"className":194,"code":195,"language":196,"meta":197,"style":197},"language-python shiki shiki-themes github-dark","from pathlib import Path\nfrom collections import Counter\nfrom qgis.core import QgsRasterLayer\n\ntiles = sorted(Path(\"\u002Fdata\u002Fdem\u002Ftiles\").glob(\"dem_*.tif\"))\ninfo = []\nfor p in tiles:\n    lyr = QgsRasterLayer(str(p), p.stem)\n    if not lyr.isValid():\n        print(\"cannot open\", p.name)\n        continue\n    prov = lyr.dataProvider()\n    info.append({\n        \"path\": str(p), \"crs\": lyr.crs().authid(),\n        \"res\": (round(lyr.rasterUnitsPerPixelX(), 6), round(lyr.rasterUnitsPerPixelY(), 6)),\n        \"dtype\": prov.dataType(1), \"bands\": lyr.bandCount(),\n        \"nodata\": prov.sourceNoDataValue(1) if prov.sourceHasNoDataValue(1) else None,\n    })\n\nfor key in (\"crs\", \"res\", \"dtype\", \"bands\", \"nodata\"):\n    print(key, Counter(str(i[key]) for i in info).most_common())\n","python","",[27,199,200,218,231,244,251,280,291,306,323,335,350,356,367,373,393,424,444,477,483,488,526],{"__ignoreMap":197},[201,202,204,208,212,215],"span",{"class":106,"line":203},1,[201,205,207],{"class":206},"snl16","from",[201,209,211],{"class":210},"s95oV"," pathlib ",[201,213,214],{"class":206},"import",[201,216,217],{"class":210}," Path\n",[201,219,221,223,226,228],{"class":106,"line":220},2,[201,222,207],{"class":206},[201,224,225],{"class":210}," collections ",[201,227,214],{"class":206},[201,229,230],{"class":210}," Counter\n",[201,232,234,236,239,241],{"class":106,"line":233},3,[201,235,207],{"class":206},[201,237,238],{"class":210}," qgis.core ",[201,240,214],{"class":206},[201,242,243],{"class":210}," QgsRasterLayer\n",[201,245,247],{"class":106,"line":246},4,[201,248,250],{"emptyLinePlaceholder":249},true,"\n",[201,252,254,257,260,264,267,271,274,277],{"class":106,"line":253},5,[201,255,256],{"class":210},"tiles ",[201,258,259],{"class":206},"=",[201,261,263],{"class":262},"sDLfK"," sorted",[201,265,266],{"class":210},"(Path(",[201,268,270],{"class":269},"sU2Wk","\"\u002Fdata\u002Fdem\u002Ftiles\"",[201,272,273],{"class":210},").glob(",[201,275,276],{"class":269},"\"dem_*.tif\"",[201,278,279],{"class":210},"))\n",[201,281,283,286,288],{"class":106,"line":282},6,[201,284,285],{"class":210},"info ",[201,287,259],{"class":206},[201,289,290],{"class":210}," []\n",[201,292,294,297,300,303],{"class":106,"line":293},7,[201,295,296],{"class":206},"for",[201,298,299],{"class":210}," p ",[201,301,302],{"class":206},"in",[201,304,305],{"class":210}," tiles:\n",[201,307,309,312,314,317,320],{"class":106,"line":308},8,[201,310,311],{"class":210},"    lyr ",[201,313,259],{"class":206},[201,315,316],{"class":210}," QgsRasterLayer(",[201,318,319],{"class":262},"str",[201,321,322],{"class":210},"(p), p.stem)\n",[201,324,326,329,332],{"class":106,"line":325},9,[201,327,328],{"class":206},"    if",[201,330,331],{"class":206}," not",[201,333,334],{"class":210}," lyr.isValid():\n",[201,336,338,341,344,347],{"class":106,"line":337},10,[201,339,340],{"class":262},"        print",[201,342,343],{"class":210},"(",[201,345,346],{"class":269},"\"cannot open\"",[201,348,349],{"class":210},", p.name)\n",[201,351,353],{"class":106,"line":352},11,[201,354,355],{"class":206},"        continue\n",[201,357,359,362,364],{"class":106,"line":358},12,[201,360,361],{"class":210},"    prov ",[201,363,259],{"class":206},[201,365,366],{"class":210}," lyr.dataProvider()\n",[201,368,370],{"class":106,"line":369},13,[201,371,372],{"class":210},"    info.append({\n",[201,374,376,379,382,384,387,390],{"class":106,"line":375},14,[201,377,378],{"class":269},"        \"path\"",[201,380,381],{"class":210},": ",[201,383,319],{"class":262},[201,385,386],{"class":210},"(p), ",[201,388,389],{"class":269},"\"crs\"",[201,391,392],{"class":210},": lyr.crs().authid(),\n",[201,394,396,399,402,405,408,411,414,416,419,421],{"class":106,"line":395},15,[201,397,398],{"class":269},"        \"res\"",[201,400,401],{"class":210},": (",[201,403,404],{"class":262},"round",[201,406,407],{"class":210},"(lyr.rasterUnitsPerPixelX(), ",[201,409,410],{"class":262},"6",[201,412,413],{"class":210},"), ",[201,415,404],{"class":262},[201,417,418],{"class":210},"(lyr.rasterUnitsPerPixelY(), ",[201,420,410],{"class":262},[201,422,423],{"class":210},")),\n",[201,425,427,430,433,436,438,441],{"class":106,"line":426},16,[201,428,429],{"class":269},"        \"dtype\"",[201,431,432],{"class":210},": prov.dataType(",[201,434,435],{"class":262},"1",[201,437,413],{"class":210},[201,439,440],{"class":269},"\"bands\"",[201,442,443],{"class":210},": lyr.bandCount(),\n",[201,445,447,450,453,455,458,461,464,466,468,471,474],{"class":106,"line":446},17,[201,448,449],{"class":269},"        \"nodata\"",[201,451,452],{"class":210},": prov.sourceNoDataValue(",[201,454,435],{"class":262},[201,456,457],{"class":210},") ",[201,459,460],{"class":206},"if",[201,462,463],{"class":210}," prov.sourceHasNoDataValue(",[201,465,435],{"class":262},[201,467,457],{"class":210},[201,469,470],{"class":206},"else",[201,472,473],{"class":262}," None",[201,475,476],{"class":210},",\n",[201,478,480],{"class":106,"line":479},18,[201,481,482],{"class":210},"    })\n",[201,484,486],{"class":106,"line":485},19,[201,487,250],{"emptyLinePlaceholder":249},[201,489,491,493,496,498,501,503,506,509,511,514,516,518,520,523],{"class":106,"line":490},20,[201,492,296],{"class":206},[201,494,495],{"class":210}," key ",[201,497,302],{"class":206},[201,499,500],{"class":210}," (",[201,502,389],{"class":269},[201,504,505],{"class":210},", ",[201,507,508],{"class":269},"\"res\"",[201,510,505],{"class":210},[201,512,513],{"class":269},"\"dtype\"",[201,515,505],{"class":210},[201,517,440],{"class":269},[201,519,505],{"class":210},[201,521,522],{"class":269},"\"nodata\"",[201,524,525],{"class":210},"):\n",[201,527,529,532,535,537,540,542,545,547],{"class":106,"line":528},21,[201,530,531],{"class":262},"    print",[201,533,534],{"class":210},"(key, Counter(",[201,536,319],{"class":262},[201,538,539],{"class":210},"(i[key]) ",[201,541,296],{"class":206},[201,543,544],{"class":210}," i ",[201,546,302],{"class":206},[201,548,549],{"class":210}," info).most_common())\n",[14,551,552,556,557,561,562,566],{},[553,554,555],"strong",{},"Breakdown:"," Printing a count of distinct values for each property shows at a glance whether the set is uniform. One line per property with a single value means the tiles are compatible; any property with two or more values names the problem. Mixed CRSs need reprojection first, as in ",[21,558,560],{"href":559},"\u002Fspatial-data-processing-automation\u002Fcoordinate-reference-systems\u002Fbatch-reprojecting-raster-datasets\u002F","batch reprojecting raster datasets",". Mixed resolutions need resampling to a common pixel size, covered in ",[21,563,565],{"href":564},"\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fresample-and-align-rasters-pyqgis\u002F","resampling and aligning rasters",". Mixed NoData values can be unified during the merge, as below.",[164,568,570],{"id":569},"merge-into-one-geotiff","Merge into one GeoTIFF",[14,572,573,575],{},[27,574,29],{}," reads the tiles and writes one raster covering their combined extent. Declaring NoData and choosing creation options decide how usable the result is.",[192,577,579],{"className":194,"code":578,"language":196,"meta":197,"style":197},"import processing\n\npaths = [i[\"path\"] for i in info]\nout = processing.run(\"gdal:merge\", {\n    \"INPUT\": paths,\n    \"PCT\": False,\n    \"SEPARATE\": False,\n    \"NODATA_INPUT\": -9999,\n    \"NODATA_OUTPUT\": -9999,\n    \"DATA_TYPE\": 5,                     # Float32\n    \"OPTIONS\": \"COMPRESS=DEFLATE|PREDICTOR=3|TILED=YES|BIGTIFF=IF_SAFER\",\n    \"EXTRA\": \"\",\n    \"OUTPUT\": \"\u002Fdata\u002Fdem\u002Fdem_mosaic.tif\",\n})[\"OUTPUT\"]\n\nmosaic = QgsRasterLayer(out, \"DEM mosaic\")\nprint(mosaic.width(), \"x\", mosaic.height(), \"pixels;\", mosaic.extent().toString(0))\n",[27,580,581,588,592,617,633,641,653,664,679,692,708,720,732,744,755,759,775],{"__ignoreMap":197},[201,582,583,585],{"class":106,"line":203},[201,584,214],{"class":206},[201,586,587],{"class":210}," processing\n",[201,589,590],{"class":106,"line":220},[201,591,250],{"emptyLinePlaceholder":249},[201,593,594,597,599,602,605,608,610,612,614],{"class":106,"line":233},[201,595,596],{"class":210},"paths ",[201,598,259],{"class":206},[201,600,601],{"class":210}," [i[",[201,603,604],{"class":269},"\"path\"",[201,606,607],{"class":210},"] ",[201,609,296],{"class":206},[201,611,544],{"class":210},[201,613,302],{"class":206},[201,615,616],{"class":210}," info]\n",[201,618,619,622,624,627,630],{"class":106,"line":246},[201,620,621],{"class":210},"out ",[201,623,259],{"class":206},[201,625,626],{"class":210}," processing.run(",[201,628,629],{"class":269},"\"gdal:merge\"",[201,631,632],{"class":210},", {\n",[201,634,635,638],{"class":106,"line":253},[201,636,637],{"class":269},"    \"INPUT\"",[201,639,640],{"class":210},": paths,\n",[201,642,643,646,648,651],{"class":106,"line":282},[201,644,645],{"class":269},"    \"PCT\"",[201,647,381],{"class":210},[201,649,650],{"class":262},"False",[201,652,476],{"class":210},[201,654,655,658,660,662],{"class":106,"line":293},[201,656,657],{"class":269},"    \"SEPARATE\"",[201,659,381],{"class":210},[201,661,650],{"class":262},[201,663,476],{"class":210},[201,665,666,669,671,674,677],{"class":106,"line":308},[201,667,668],{"class":269},"    \"NODATA_INPUT\"",[201,670,381],{"class":210},[201,672,673],{"class":206},"-",[201,675,676],{"class":262},"9999",[201,678,476],{"class":210},[201,680,681,684,686,688,690],{"class":106,"line":325},[201,682,683],{"class":269},"    \"NODATA_OUTPUT\"",[201,685,381],{"class":210},[201,687,673],{"class":206},[201,689,676],{"class":262},[201,691,476],{"class":210},[201,693,694,697,699,701,704],{"class":106,"line":337},[201,695,696],{"class":269},"    \"DATA_TYPE\"",[201,698,381],{"class":210},[201,700,64],{"class":262},[201,702,703],{"class":210},",                     ",[201,705,707],{"class":706},"sjoCn","# Float32\n",[201,709,710,713,715,718],{"class":106,"line":352},[201,711,712],{"class":269},"    \"OPTIONS\"",[201,714,381],{"class":210},[201,716,717],{"class":269},"\"COMPRESS=DEFLATE|PREDICTOR=3|TILED=YES|BIGTIFF=IF_SAFER\"",[201,719,476],{"class":210},[201,721,722,725,727,730],{"class":106,"line":358},[201,723,724],{"class":269},"    \"EXTRA\"",[201,726,381],{"class":210},[201,728,729],{"class":269},"\"\"",[201,731,476],{"class":210},[201,733,734,737,739,742],{"class":106,"line":369},[201,735,736],{"class":269},"    \"OUTPUT\"",[201,738,381],{"class":210},[201,740,741],{"class":269},"\"\u002Fdata\u002Fdem\u002Fdem_mosaic.tif\"",[201,743,476],{"class":210},[201,745,746,749,752],{"class":106,"line":375},[201,747,748],{"class":210},"})[",[201,750,751],{"class":269},"\"OUTPUT\"",[201,753,754],{"class":210},"]\n",[201,756,757],{"class":106,"line":395},[201,758,250],{"emptyLinePlaceholder":249},[201,760,761,764,766,769,772],{"class":106,"line":426},[201,762,763],{"class":210},"mosaic ",[201,765,259],{"class":206},[201,767,768],{"class":210}," QgsRasterLayer(out, ",[201,770,771],{"class":269},"\"DEM mosaic\"",[201,773,774],{"class":210},")\n",[201,776,777,780,783,786,789,792,795,797],{"class":106,"line":446},[201,778,779],{"class":262},"print",[201,781,782],{"class":210},"(mosaic.width(), ",[201,784,785],{"class":269},"\"x\"",[201,787,788],{"class":210},", mosaic.height(), ",[201,790,791],{"class":269},"\"pixels;\"",[201,793,794],{"class":210},", mosaic.extent().toString(",[201,796,51],{"class":262},[201,798,279],{"class":210},[14,800,801,803,804,807,808,811,812,815,816,819],{},[553,802,555],{}," ",[27,805,806],{},"NODATA_INPUT"," tells the merge which input value means \"no data\", so those pixels do not overwrite valid data from another tile where tiles overlap. ",[27,809,810],{},"NODATA_OUTPUT"," declares the value in the result, so gaps between tiles are transparent rather than black. The data type enum follows GDAL's order — 5 is Float32 in the Processing dialog's list; check ",[27,813,814],{},"processing.algorithmHelp(\"gdal:merge\")"," on your version. Deflate compression with predictor 3 shrinks floating-point elevation data substantially, tiling makes the file fast to display and clip, and ",[27,817,818],{},"BIGTIFF=IF_SAFER"," avoids failure when the output exceeds 4 GB.",[164,821,823],{"id":822},"decide-what-happens-where-tiles-overlap","Decide what happens where tiles overlap",[14,825,826,827,829],{},"Tiles often overlap by a few pixels, and the values there may differ slightly — different acquisition dates, different processing. ",[27,828,29],{}," lets the last tile win, so the order of the input list decides the result.",[14,831,832],{},[34,833,836,839,842,845,848,857,861,864,869,873,878,882,887,891],{"viewBox":834,"role":37,"ariaLabel":835,"xmlns":39},"0 0 760 240","Two overlapping tiles where the later tile in the input list overwrites the earlier one, except where the later tile has NoData",[41,837,838],{},"Last tile wins in overlaps",[45,840,841],{},"Two tiles overlap in a strip. With gdal:merge the tile listed later overwrites the earlier one in the overlap, unless the later tile's pixel is NoData. Ordering the list, for example by acquisition date so the newest is last, makes the outcome deliberate. A VRT follows the same rule with the last source on top.",[49,843],{"x":51,"y":51,"width":52,"height":844,"fill":54},"240",[73,846,847],{"x":75,"y":76,"style":77,"fill":78,"textAnchor":79},"Order the list to choose the winner",[49,849],{"x":850,"y":851,"width":852,"height":853,"rx":854,"fill":855,"stroke":856,"style":89},"80","70","260","120","4","#eef7f4","#0f766e",[49,858],{"x":859,"y":851,"width":852,"height":853,"rx":854,"fill":153,"fillOpacity":860,"stroke":154,"style":89},"280",0.7,[49,862],{"x":859,"y":851,"width":84,"height":853,"rx":51,"fill":154,"stroke":154,"style":863},"stroke-width:1",[73,865,868],{"x":115,"y":866,"style":867,"fill":856,"textAnchor":79},"136","text-anchor:middle;font-size:11.0px;font-family:sans-serif","tile A (2019)",[73,870,872],{"x":871,"y":866,"style":867,"fill":154,"textAnchor":79},"460","tile B (2023)",[73,874,877],{"x":875,"y":876,"style":99,"fill":154,"textAnchor":79},"310","214","overlap: B wins",[49,879],{"x":880,"y":851,"width":881,"height":853,"rx":63,"fill":87,"stroke":88,"style":89},"572","164",[73,883,886],{"x":884,"y":885,"style":94,"fill":78,"textAnchor":79},"654","109.78","sort inputs",[73,888,890],{"x":884,"y":889,"style":99,"fill":88,"textAnchor":79},"133.78","oldest first",[73,892,894],{"x":884,"y":893,"style":99,"fill":88,"textAnchor":79},"157.78","newest last",[192,896,898],{"className":194,"code":897,"language":196,"meta":197,"style":197},"import re\n\ndef acquisition_year(path):\n    m = re.search(r\"_(\\d{4})\\.tif$\", path)\n    return int(m.group(1)) if m else 0\n\nordered = sorted(paths, key=acquisition_year)      # newest last → newest wins\nprocessing.run(\"gdal:merge\", {\n    \"INPUT\": ordered, \"NODATA_INPUT\": -9999, \"NODATA_OUTPUT\": -9999,\n    \"DATA_TYPE\": 5, \"OPTIONS\": \"COMPRESS=DEFLATE|PREDICTOR=3|TILED=YES\",\n    \"OUTPUT\": \"\u002Fdata\u002Fdem\u002Fdem_mosaic_newest.tif\"})\n",[27,899,900,907,911,923,967,993,997,1021,1030,1059,1079],{"__ignoreMap":197},[201,901,902,904],{"class":106,"line":203},[201,903,214],{"class":206},[201,905,906],{"class":210}," re\n",[201,908,909],{"class":106,"line":220},[201,910,250],{"emptyLinePlaceholder":249},[201,912,913,916,920],{"class":106,"line":233},[201,914,915],{"class":206},"def",[201,917,919],{"class":918},"svObZ"," acquisition_year",[201,921,922],{"class":210},"(path):\n",[201,924,925,928,930,933,936,939,943,946,949,952,956,959,962,964],{"class":106,"line":246},[201,926,927],{"class":210},"    m ",[201,929,259],{"class":206},[201,931,932],{"class":210}," re.search(",[201,934,935],{"class":206},"r",[201,937,938],{"class":269},"\"",[201,940,942],{"class":941},"sns5M","_",[201,944,945],{"class":262},"(\\d",[201,947,948],{"class":206},"{4}",[201,950,951],{"class":262},")",[201,953,955],{"class":954},"sRjNt","\\.",[201,957,958],{"class":941},"tif",[201,960,961],{"class":262},"$",[201,963,938],{"class":269},[201,965,966],{"class":210},", path)\n",[201,968,969,972,975,978,980,983,985,988,990],{"class":106,"line":253},[201,970,971],{"class":206},"    return",[201,973,974],{"class":262}," int",[201,976,977],{"class":210},"(m.group(",[201,979,435],{"class":262},[201,981,982],{"class":210},")) ",[201,984,460],{"class":206},[201,986,987],{"class":210}," m ",[201,989,470],{"class":206},[201,991,992],{"class":262}," 0\n",[201,994,995],{"class":106,"line":282},[201,996,250],{"emptyLinePlaceholder":249},[201,998,999,1002,1004,1006,1009,1013,1015,1018],{"class":106,"line":293},[201,1000,1001],{"class":210},"ordered ",[201,1003,259],{"class":206},[201,1005,263],{"class":262},[201,1007,1008],{"class":210},"(paths, ",[201,1010,1012],{"class":1011},"s9osk","key",[201,1014,259],{"class":206},[201,1016,1017],{"class":210},"acquisition_year)      ",[201,1019,1020],{"class":706},"# newest last → newest wins\n",[201,1022,1023,1026,1028],{"class":106,"line":308},[201,1024,1025],{"class":210},"processing.run(",[201,1027,629],{"class":269},[201,1029,632],{"class":210},[201,1031,1032,1034,1037,1040,1042,1044,1046,1048,1051,1053,1055,1057],{"class":106,"line":325},[201,1033,637],{"class":269},[201,1035,1036],{"class":210},": ordered, ",[201,1038,1039],{"class":269},"\"NODATA_INPUT\"",[201,1041,381],{"class":210},[201,1043,673],{"class":206},[201,1045,676],{"class":262},[201,1047,505],{"class":210},[201,1049,1050],{"class":269},"\"NODATA_OUTPUT\"",[201,1052,381],{"class":210},[201,1054,673],{"class":206},[201,1056,676],{"class":262},[201,1058,476],{"class":210},[201,1060,1061,1063,1065,1067,1069,1072,1074,1077],{"class":106,"line":337},[201,1062,696],{"class":269},[201,1064,381],{"class":210},[201,1066,64],{"class":262},[201,1068,505],{"class":210},[201,1070,1071],{"class":269},"\"OPTIONS\"",[201,1073,381],{"class":210},[201,1075,1076],{"class":269},"\"COMPRESS=DEFLATE|PREDICTOR=3|TILED=YES\"",[201,1078,476],{"class":210},[201,1080,1081,1083,1085,1088],{"class":106,"line":352},[201,1082,736],{"class":269},[201,1084,381],{"class":210},[201,1086,1087],{"class":269},"\"\u002Fdata\u002Fdem\u002Fdem_mosaic_newest.tif\"",[201,1089,1090],{"class":210},"})\n",[14,1092,1093,1095],{},[553,1094,555],{}," Sorting by acquisition year, extracted here from the file name, puts the newest tiles last so their values win in overlaps. Any rule can be encoded the same way — highest accuracy class, lowest cloud cover. Pixels that are NoData in the later tile do not overwrite valid values from earlier ones, so a ragged-edged newer scene fills only where it has data. For blending rather than overwriting — averaging overlaps to hide seams — compute the overlap separately with the raster calculator, which is rarely worth the effort for DEMs but sometimes matters for imagery.",[164,1097,1099],{"id":1098},"check-the-result","Check the result",[14,1101,1102],{},"A merged raster should have no unexpected holes, no visible seams and statistics consistent with the tiles.",[192,1104,1106],{"className":194,"code":1105,"language":196,"meta":197,"style":197},"from qgis.core import QgsRasterBandStats\n\nstats = mosaic.dataProvider().bandStatistics(1, QgsRasterBandStats.All)\nprint(f\"min {stats.minimumValue:.1f}  max {stats.maximumValue:.1f}  mean {stats.mean:.1f}\")\n\nblock = mosaic.dataProvider().block(1, mosaic.extent(), 400, 400)\nnodata_share = sum(block.isNoData(r, c) for r in range(400) for c in range(400)) \u002F 160000\nprint(f\"NoData in a 400×400 overview sample: {nodata_share:.1%}\")\n",[27,1107,1108,1119,1123,1138,1190,1194,1218,1268],{"__ignoreMap":197},[201,1109,1110,1112,1114,1116],{"class":106,"line":203},[201,1111,207],{"class":206},[201,1113,238],{"class":210},[201,1115,214],{"class":206},[201,1117,1118],{"class":210}," QgsRasterBandStats\n",[201,1120,1121],{"class":106,"line":220},[201,1122,250],{"emptyLinePlaceholder":249},[201,1124,1125,1128,1130,1133,1135],{"class":106,"line":233},[201,1126,1127],{"class":210},"stats ",[201,1129,259],{"class":206},[201,1131,1132],{"class":210}," mosaic.dataProvider().bandStatistics(",[201,1134,435],{"class":262},[201,1136,1137],{"class":210},", QgsRasterBandStats.All)\n",[201,1139,1140,1142,1144,1147,1150,1153,1156,1159,1162,1165,1167,1170,1172,1174,1177,1179,1182,1184,1186,1188],{"class":106,"line":246},[201,1141,779],{"class":262},[201,1143,343],{"class":210},[201,1145,1146],{"class":206},"f",[201,1148,1149],{"class":269},"\"min ",[201,1151,1152],{"class":262},"{",[201,1154,1155],{"class":210},"stats.minimumValue",[201,1157,1158],{"class":206},":.1f",[201,1160,1161],{"class":262},"}",[201,1163,1164],{"class":269},"  max ",[201,1166,1152],{"class":262},[201,1168,1169],{"class":210},"stats.maximumValue",[201,1171,1158],{"class":206},[201,1173,1161],{"class":262},[201,1175,1176],{"class":269},"  mean ",[201,1178,1152],{"class":262},[201,1180,1181],{"class":210},"stats.mean",[201,1183,1158],{"class":206},[201,1185,1161],{"class":262},[201,1187,938],{"class":269},[201,1189,774],{"class":210},[201,1191,1192],{"class":106,"line":253},[201,1193,250],{"emptyLinePlaceholder":249},[201,1195,1196,1199,1201,1204,1206,1209,1212,1214,1216],{"class":106,"line":282},[201,1197,1198],{"class":210},"block ",[201,1200,259],{"class":206},[201,1202,1203],{"class":210}," mosaic.dataProvider().block(",[201,1205,435],{"class":262},[201,1207,1208],{"class":210},", mosaic.extent(), ",[201,1210,1211],{"class":262},"400",[201,1213,505],{"class":210},[201,1215,1211],{"class":262},[201,1217,774],{"class":210},[201,1219,1220,1223,1225,1228,1231,1233,1236,1238,1241,1243,1245,1247,1249,1252,1254,1256,1258,1260,1262,1265],{"class":106,"line":293},[201,1221,1222],{"class":210},"nodata_share ",[201,1224,259],{"class":206},[201,1226,1227],{"class":262}," sum",[201,1229,1230],{"class":210},"(block.isNoData(r, c) ",[201,1232,296],{"class":206},[201,1234,1235],{"class":210}," r ",[201,1237,302],{"class":206},[201,1239,1240],{"class":262}," range",[201,1242,343],{"class":210},[201,1244,1211],{"class":262},[201,1246,457],{"class":210},[201,1248,296],{"class":206},[201,1250,1251],{"class":210}," c ",[201,1253,302],{"class":206},[201,1255,1240],{"class":262},[201,1257,343],{"class":210},[201,1259,1211],{"class":262},[201,1261,982],{"class":210},[201,1263,1264],{"class":206},"\u002F",[201,1266,1267],{"class":262}," 160000\n",[201,1269,1270,1272,1274,1276,1279,1281,1284,1287,1289,1291],{"class":106,"line":308},[201,1271,779],{"class":262},[201,1273,343],{"class":210},[201,1275,1146],{"class":206},[201,1277,1278],{"class":269},"\"NoData in a 400×400 overview sample: ",[201,1280,1152],{"class":262},[201,1282,1283],{"class":210},"nodata_share",[201,1285,1286],{"class":206},":.1%",[201,1288,1161],{"class":262},[201,1290,938],{"class":269},[201,1292,774],{"class":210},[14,1294,1295,1297,1298,1302],{},[553,1296,555],{}," Minimum and maximum outside the tiles' range point to a NoData value that was not declared — a −9999 counted as an elevation. Sampling a low-resolution overview of the mosaic for NoData shows whether gaps are where expected (sea, outside the survey area) or appear as lines between tiles, which indicates misaligned tiles or inconsistent NoData. A hillshade of the mosaic, as in ",[21,1299,1301],{"href":1300},"\u002Fspatial-data-processing-automation\u002Fterrain-and-interpolation-analysis\u002Fgenerate-slope-aspect-hillshade-pyqgis\u002F","generating slope, aspect and hillshade",", reveals seams no statistic shows.",[164,1304,1306],{"id":1305},"keep-the-mosaic-reproducible","Keep the mosaic reproducible",[14,1308,1309],{},"A mosaic is a derived product, and questions about it come later: which tiles went in, which won in overlaps, what NoData means. Recording that next to the file costs one small step.",[192,1311,1313],{"className":194,"code":1312,"language":196,"meta":197,"style":197},"import json\nfrom datetime import datetime, timezone\n\nrecord = {\n    \"created\": datetime.now(timezone.utc).isoformat(timespec=\"seconds\"),\n    \"inputs\": ordered, \"nodata\": -9999, \"data_type\": \"Float32\",\n    \"overlap_rule\": \"later input wins; inputs sorted by acquisition year\",\n}\nwith open(\"\u002Fdata\u002Fdem\u002Fdem_mosaic_newest.json\", \"w\") as fh:\n    json.dump(record, fh, indent=2)\n",[27,1314,1315,1322,1334,1338,1348,1367,1394,1406,1411,1437],{"__ignoreMap":197},[201,1316,1317,1319],{"class":106,"line":203},[201,1318,214],{"class":206},[201,1320,1321],{"class":210}," json\n",[201,1323,1324,1326,1329,1331],{"class":106,"line":220},[201,1325,207],{"class":206},[201,1327,1328],{"class":210}," datetime ",[201,1330,214],{"class":206},[201,1332,1333],{"class":210}," datetime, timezone\n",[201,1335,1336],{"class":106,"line":233},[201,1337,250],{"emptyLinePlaceholder":249},[201,1339,1340,1343,1345],{"class":106,"line":246},[201,1341,1342],{"class":210},"record ",[201,1344,259],{"class":206},[201,1346,1347],{"class":210}," {\n",[201,1349,1350,1353,1356,1359,1361,1364],{"class":106,"line":253},[201,1351,1352],{"class":269},"    \"created\"",[201,1354,1355],{"class":210},": datetime.now(timezone.utc).isoformat(",[201,1357,1358],{"class":1011},"timespec",[201,1360,259],{"class":206},[201,1362,1363],{"class":269},"\"seconds\"",[201,1365,1366],{"class":210},"),\n",[201,1368,1369,1372,1374,1376,1378,1380,1382,1384,1387,1389,1392],{"class":106,"line":282},[201,1370,1371],{"class":269},"    \"inputs\"",[201,1373,1036],{"class":210},[201,1375,522],{"class":269},[201,1377,381],{"class":210},[201,1379,673],{"class":206},[201,1381,676],{"class":262},[201,1383,505],{"class":210},[201,1385,1386],{"class":269},"\"data_type\"",[201,1388,381],{"class":210},[201,1390,1391],{"class":269},"\"Float32\"",[201,1393,476],{"class":210},[201,1395,1396,1399,1401,1404],{"class":106,"line":293},[201,1397,1398],{"class":269},"    \"overlap_rule\"",[201,1400,381],{"class":210},[201,1402,1403],{"class":269},"\"later input wins; inputs sorted by acquisition year\"",[201,1405,476],{"class":210},[201,1407,1408],{"class":106,"line":308},[201,1409,1410],{"class":210},"}\n",[201,1412,1413,1416,1419,1421,1424,1426,1429,1431,1434],{"class":106,"line":325},[201,1414,1415],{"class":206},"with",[201,1417,1418],{"class":262}," open",[201,1420,343],{"class":210},[201,1422,1423],{"class":269},"\"\u002Fdata\u002Fdem\u002Fdem_mosaic_newest.json\"",[201,1425,505],{"class":210},[201,1427,1428],{"class":269},"\"w\"",[201,1430,457],{"class":210},[201,1432,1433],{"class":206},"as",[201,1435,1436],{"class":210}," fh:\n",[201,1438,1439,1442,1445,1447,1450],{"class":106,"line":337},[201,1440,1441],{"class":210},"    json.dump(record, fh, ",[201,1443,1444],{"class":1011},"indent",[201,1446,259],{"class":206},[201,1448,1449],{"class":262},"2",[201,1451,774],{"class":210},[14,1453,1454,1456],{},[553,1455,555],{}," A JSON sidecar listing inputs in merge order, the NoData value and the overlap rule lets anyone reproduce the mosaic or explain an odd value at a seam. It is also the natural place to note tile versions when a supplier reissues tiles. For mosaics rebuilt on a schedule, write the record in the same script that runs the merge, so the two can never disagree.",[164,1458,1460],{"id":1459},"when-a-vrt-is-better-than-merging","When a VRT is better than merging",[14,1462,1463],{},"Merging copies every pixel into a new file. A virtual raster (VRT) is a small XML file that references the tiles and presents them as one raster, with no copying.",[14,1465,1466],{},[34,1467,1470,1473,1476,1479,1482,1486,1491,1495,1499,1503,1506,1510,1513,1516],{"viewBox":1468,"role":37,"ariaLabel":1469,"xmlns":39},"0 0 760 230","A merged GeoTIFF as a portable standalone copy compared with a VRT that references tiles without duplication",[41,1471,1472],{},"Merge or VRT",[45,1474,1475],{},"A merged GeoTIFF is a standalone copy: portable, fast to read, but duplicates storage and goes stale when tiles are updated. A VRT is a few kilobytes referencing the original tiles: no duplication, always current, but depends on the tiles staying in place and can be slower over many small files. Many workflows build a VRT for analysis and merge only for delivery.",[49,1477],{"x":51,"y":51,"width":52,"height":1478,"fill":54},"230",[73,1480,1481],{"x":75,"y":76,"style":77,"fill":78,"textAnchor":79},"Copy everything, or reference it",[49,1483],{"x":83,"y":1484,"width":1485,"height":86,"rx":63,"fill":138,"stroke":139,"style":89},"56","340",[73,1487,1490],{"x":1488,"y":1489,"style":94,"fill":144,"textAnchor":79},"194","92.78","merged GeoTIFF",[73,1492,1494],{"x":1488,"y":1493,"style":99,"fill":71,"textAnchor":79},"120.78","standalone, portable",[73,1496,1498],{"x":1488,"y":1497,"style":99,"fill":71,"textAnchor":79},"148.78","fast reads",[73,1500,1502],{"x":1488,"y":1501,"style":99,"fill":88,"textAnchor":79},"176.78","doubles storage, can go stale",[49,1504],{"x":1505,"y":1484,"width":1485,"height":86,"rx":63,"fill":153,"stroke":154,"style":89},"396",[73,1507,1509],{"x":1508,"y":1489,"style":94,"fill":154,"textAnchor":79},"566","VRT",[73,1511,1512],{"x":1508,"y":1493,"style":99,"fill":71,"textAnchor":79},"kilobytes, no copy",[73,1514,1515],{"x":1508,"y":1497,"style":99,"fill":71,"textAnchor":79},"always current",[73,1517,1518],{"x":1508,"y":1501,"style":99,"fill":88,"textAnchor":79},"tiles must stay put",[14,1520,1521,1522,1526],{},"For analysis on a machine that holds the tiles, a VRT is usually the better choice: it is instant to build, needs no extra space and reflects tile updates automatically. For delivering a mosaic to someone else, or for very many small tiles where opening hundreds of files is slow, merge. ",[21,1523,1525],{"href":1524},"\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fbuild-virtual-raster-vrt-pyqgis\u002F","Building a virtual raster"," covers the VRT route, and a common pattern combines both: build a VRT, check it, then translate it to a GeoTIFF in one step.",[164,1528,1530],{"id":1529},"merge-only-what-an-area-needs","Merge only what an area needs",[14,1532,1533],{},"For a study area covering a few tiles out of hundreds, select the intersecting tiles first rather than merging the whole set and clipping afterwards.",[192,1535,1537],{"className":194,"code":1536,"language":196,"meta":197,"style":197},"from qgis.core import QgsProject, QgsRectangle\n\narea = QgsProject.instance().mapLayersByName(\"catchment\")[0]\ntarget = area.extent()\ntarget.grow(100)                                     # small margin in map units\n\nneeded = []\nfor i in info:\n    ext = QgsRasterLayer(i[\"path\"], \"x\").extent()\n    if ext.intersects(target):\n        needed.append(i[\"path\"])\nprint(len(needed), \"of\", len(info), \"tiles intersect the catchment\")\n",[27,1538,1539,1550,1554,1574,1584,1598,1602,1611,1622,1642,1649,1659],{"__ignoreMap":197},[201,1540,1541,1543,1545,1547],{"class":106,"line":203},[201,1542,207],{"class":206},[201,1544,238],{"class":210},[201,1546,214],{"class":206},[201,1548,1549],{"class":210}," QgsProject, QgsRectangle\n",[201,1551,1552],{"class":106,"line":220},[201,1553,250],{"emptyLinePlaceholder":249},[201,1555,1556,1559,1561,1564,1567,1570,1572],{"class":106,"line":233},[201,1557,1558],{"class":210},"area ",[201,1560,259],{"class":206},[201,1562,1563],{"class":210}," QgsProject.instance().mapLayersByName(",[201,1565,1566],{"class":269},"\"catchment\"",[201,1568,1569],{"class":210},")[",[201,1571,51],{"class":262},[201,1573,754],{"class":210},[201,1575,1576,1579,1581],{"class":106,"line":246},[201,1577,1578],{"class":210},"target ",[201,1580,259],{"class":206},[201,1582,1583],{"class":210}," area.extent()\n",[201,1585,1586,1589,1592,1595],{"class":106,"line":253},[201,1587,1588],{"class":210},"target.grow(",[201,1590,1591],{"class":262},"100",[201,1593,1594],{"class":210},")                                     ",[201,1596,1597],{"class":706},"# small margin in map units\n",[201,1599,1600],{"class":106,"line":282},[201,1601,250],{"emptyLinePlaceholder":249},[201,1603,1604,1607,1609],{"class":106,"line":293},[201,1605,1606],{"class":210},"needed ",[201,1608,259],{"class":206},[201,1610,290],{"class":210},[201,1612,1613,1615,1617,1619],{"class":106,"line":308},[201,1614,296],{"class":206},[201,1616,544],{"class":210},[201,1618,302],{"class":206},[201,1620,1621],{"class":210}," info:\n",[201,1623,1624,1627,1629,1632,1634,1637,1639],{"class":106,"line":325},[201,1625,1626],{"class":210},"    ext ",[201,1628,259],{"class":206},[201,1630,1631],{"class":210}," QgsRasterLayer(i[",[201,1633,604],{"class":269},[201,1635,1636],{"class":210},"], ",[201,1638,785],{"class":269},[201,1640,1641],{"class":210},").extent()\n",[201,1643,1644,1646],{"class":106,"line":337},[201,1645,328],{"class":206},[201,1647,1648],{"class":210}," ext.intersects(target):\n",[201,1650,1651,1654,1656],{"class":106,"line":352},[201,1652,1653],{"class":210},"        needed.append(i[",[201,1655,604],{"class":269},[201,1657,1658],{"class":210},"])\n",[201,1660,1661,1663,1665,1668,1671,1674,1676,1678,1681,1684],{"class":106,"line":358},[201,1662,779],{"class":262},[201,1664,343],{"class":210},[201,1666,1667],{"class":262},"len",[201,1669,1670],{"class":210},"(needed), ",[201,1672,1673],{"class":269},"\"of\"",[201,1675,505],{"class":210},[201,1677,1667],{"class":262},[201,1679,1680],{"class":210},"(info), ",[201,1682,1683],{"class":269},"\"tiles intersect the catchment\"",[201,1685,774],{"class":210},[14,1687,1688,1690,1691,1695,1696,1699],{},[553,1689,555],{}," Comparing each tile's extent with the study area's bounding box, plus a margin so edge pixels are not lost, selects the few tiles that matter. Merging those and then ",[21,1692,1694],{"href":1693},"\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fclip-raster-by-mask-layer-pyqgis\u002F","clipping by the catchment polygon"," is far faster than processing the national set. For repeated selections, build a tile index layer once with ",[27,1697,1698],{},"gdal:tileindex"," and select by location.",[164,1701,1703],{"id":1702},"qgis-version-compatibility","QGIS version compatibility",[14,1705,1706,1708,1709,1712],{},[27,1707,29],{}," is available with the same parameters on QGIS 3.34 LTR, 3.40 LTR and QGIS 4; the data type enum and the creation ",[27,1710,1711],{},"OPTIONS"," string follow the bundled GDAL. Very long input lists are written to a temporary file list automatically on recent releases, avoiding command-line length limits on Windows.",[164,1714,1716],{"id":1715},"troubleshooting","Troubleshooting",[169,1718,1719,1730,1736,1742],{},[172,1720,1721,1724,1725,1727,1728,180],{},[553,1722,1723],{},"Black stripes between tiles."," NoData was not declared; set ",[27,1726,806],{}," and ",[27,1729,810],{},[172,1731,1732,1735],{},[553,1733,1734],{},"The mosaic is shifted or blurry in parts."," Tiles had different CRSs or resolutions; fix them before merging.",[172,1737,1738,1741],{},[553,1739,1740],{},"The merge fails on Windows with many files."," Update QGIS, or merge in batches and merge the results.",[172,1743,1744,1747,1748,1751],{},[553,1745,1746],{},"The output is huge."," Compression options were not set; use ",[27,1749,1750],{},"COMPRESS=DEFLATE"," and tiling.",[164,1753,1755],{"id":1754},"conclusion","Conclusion",[14,1757,1758],{},"Inventory tiles for CRS, resolution, data type and NoData before merging, order inputs so the right tile wins in overlaps, merge with declared NoData and compression into a tiled GeoTIFF, check statistics and seams, select only the tiles an area needs, and prefer a VRT for local analysis.",[164,1760,1762],{"id":1761},"frequently-asked-questions","Frequently Asked Questions",[14,1764,1765,1768,1769,1771,1772,1775],{},[553,1766,1767],{},"Can I merge rasters with different numbers of bands?","\nNot into one band stack with ",[27,1770,29],{},"; make band counts consistent first, or use ",[27,1773,1774],{},"SEPARATE"," to put each input in its own band.",[14,1777,1778,1781],{},[553,1779,1780],{},"Does merging change pixel values?","\nNo, unless data types differ and values are converted, or overlaps are overwritten.",[14,1783,1784,1787,1788,1791],{},[553,1785,1786],{},"How do I merge imagery with colour tables?","\nSet ",[27,1789,1790],{},"PCT"," to keep the colour table from the first input; all inputs should share it.",[14,1793,1794,1797,1800,1801,1804],{},[553,1795,1796],{},"Can I merge rasters in different CRSs directly?",[27,1798,1799],{},"gdalwarp"," can, by reprojecting during the merge; ",[27,1802,1803],{},"gdal:warpreproject"," with multiple inputs is the Processing route.",[164,1806,1808],{"id":1807},"related","Related",[169,1810,1811,1816,1821,1826,1831],{},[172,1812,1813,1815],{},[21,1814,24],{"href":23}," — the guide this recipe belongs to",[172,1817,1818],{},[21,1819,1820],{"href":1524},"Build a Virtual Raster (VRT) in PyQGIS",[172,1822,1823],{},[21,1824,1825],{"href":564},"Resample and Align Rasters in PyQGIS",[172,1827,1828],{},[21,1829,1830],{"href":1693},"Clip a Raster by a Mask Layer in PyQGIS",[172,1832,1833],{},[21,1834,1835],{"href":559},"Batch Reprojecting Raster Datasets",[1837,1838,1839],"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 .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}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}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sns5M, html code.shiki .sns5M{--shiki-default:#DBEDFF}html pre.shiki code .sRjNt, html code.shiki .sRjNt{--shiki-default:#85E89D;--shiki-default-font-weight:bold}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":197,"searchDepth":220,"depth":220,"links":1841},[1842,1843,1844,1845,1846,1847,1848,1849,1850,1851,1852,1853,1854],{"id":166,"depth":220,"text":167},{"id":186,"depth":220,"text":187},{"id":569,"depth":220,"text":570},{"id":822,"depth":220,"text":823},{"id":1098,"depth":220,"text":1099},{"id":1305,"depth":220,"text":1306},{"id":1459,"depth":220,"text":1460},{"id":1529,"depth":220,"text":1530},{"id":1702,"depth":220,"text":1703},{"id":1715,"depth":220,"text":1716},{"id":1754,"depth":220,"text":1755},{"id":1761,"depth":220,"text":1762},{"id":1807,"depth":220,"text":1808},"Combine DEM tiles, orthophoto sheets or satellite scenes into one raster with gdal:merge — checking that tiles share CRS, resolution and data type first, handling NoData and overlaps, writing a compressed tiled GeoTIFF, and knowing when a VRT is the better choice.","md",{"slug":1858,"type":1859,"breadcrumb":1860,"datePublished":1861,"dateModified":1861},"merge-raster-tiles-into-mosaic-pyqgis","article","Merge Raster Tiles into a Mosaic","2026-10-02","\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fmerge-raster-tiles-into-mosaic-pyqgis",{"title":5,"description":1855},"spatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fmerge-raster-tiles-into-mosaic-pyqgis\u002Findex","k5H7VjYqQktl174PKLMGTdFgEo23W3yJuUHdxHKInos",1790966264604]