[{"data":1,"prerenderedAt":1719},["ShallowReactive",2],{"doc:\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fbuild-virtual-raster-vrt-pyqgis":3},{"id":4,"title":5,"body":6,"description":1708,"extension":1709,"meta":1710,"navigation":253,"path":1715,"seo":1716,"stem":1717,"__hash__":1718},"docs\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fbuild-virtual-raster-vrt-pyqgis\u002Findex.md","Build a Virtual Raster (VRT) in PyQGIS",{"type":7,"value":8,"toc":1694},"minimark",[9,13,17,26,169,174,189,193,200,490,513,517,523,581,780,798,802,805,992,1005,1009,1016,1094,1188,1193,1197,1200,1456,1469,1473,1480,1546,1564,1568,1582,1586,1612,1616,1619,1623,1633,1639,1645,1651,1657,1661,1690],[10,11,5],"h1",{"id":12},"build-a-virtual-raster-vrt-in-pyqgis",[14,15,16],"p",{},"A virtual raster is a small XML file that describes how to assemble other raster files into one. QGIS and every GDAL-based tool open it like any GeoTIFF, but nothing is copied: reading a pixel from the VRT reads it from the right source file on the fly. That makes VRTs the quickest way to treat a folder of DEM tiles as one elevation model, to stack separate band files from a satellite product into one multiband image, or to give an analysis a stable layer name while the underlying tiles are updated.",[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 builds mosaic and band-stack VRTs with Processing, chooses resolution and NoData handling, keeps paths portable, adds overviews for fast display, and turns a VRT into a real file when a standalone copy is needed.",[14,27,28],{},[29,30,35,39,43,50,67,76,85,91,96,101,105,109,117,120,126,132,137,140,144,147,151,159,165],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"xmlns":34},"0 0 760 246","img","A small VRT file listing source files and their windows, from which GDAL reads only the parts needed for each requested block","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[36,37,38],"title",{},"A VRT points at files",[40,41,42],"desc",{},"A VRT file of a few kilobytes contains, for each band, a list of sources: a file path, the source window and the destination window in the virtual raster's grid. When QGIS reads a block of the VRT, GDAL works out which source files overlap that block and reads only those parts. No pixels are stored in the VRT itself.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","246","#f6f3ea",[51,52,53],"defs",{},[54,55,62],"marker",{"id":56,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"vrtIdeaArrow","0 0 10 10","8","5","7","auto-start-reverse",[63,64],"path",{"d":65,"fill":66},"M0 0 L10 5 L0 10 z","#2f3b35",[68,69,75],"text",{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"380","28","text-anchor:middle;font-size:14px;font-family:sans-serif;font-weight:bold","#17211d","middle","An index of sources, not a copy of pixels",[44,77],{"x":78,"y":79,"width":80,"height":81,"rx":58,"fill":82,"stroke":83,"style":84},"24","60","250","170","#eef7f4","#0f766e","stroke-width:2",[68,86,90],{"x":87,"y":88,"style":89,"fill":83,"textAnchor":74},"149","92.6","text-anchor:middle;font-size:11.0px;font-family:sans-serif;font-weight:bold","mosaic.vrt (4 KB)",[68,92,95],{"x":87,"y":93,"style":94,"fill":66,"textAnchor":74},"120.6","text-anchor:middle;font-size:10.0px;font-family:sans-serif","band 1:",[68,97,100],{"x":87,"y":98,"style":99,"fill":66,"textAnchor":74},"148.6","text-anchor:middle;font-size:9.5px;font-family:monospace","tile_01.tif → window",[68,102,104],{"x":87,"y":103,"style":99,"fill":66,"textAnchor":74},"176.6","tile_02.tif → window",[68,106,108],{"x":87,"y":107,"style":99,"fill":66,"textAnchor":74},"204.6","…",[110,111],"line",{"x1":112,"y1":113,"x2":114,"y2":115,"stroke":66,"style":116},"274","145","316","110","stroke-width:1.8;marker-end:url(#vrtIdeaArrow)",[110,118],{"x1":112,"y1":113,"x2":114,"y2":119,"stroke":66,"style":116},"180",[44,121],{"x":122,"y":123,"width":119,"height":123,"rx":58,"fill":124,"stroke":125,"style":84},"320","70","#fffdf7","#59645f",[68,127,131],{"x":128,"y":129,"style":130,"fill":73,"textAnchor":74},"410","97.78","text-anchor:middle;font-size:11.5px;font-family:sans-serif;font-weight:bold","tile_01.tif",[68,133,136],{"x":128,"y":134,"style":135,"fill":125,"textAnchor":74},"119.78","text-anchor:middle;font-size:10.5px;font-family:sans-serif","read part",[44,138],{"x":122,"y":139,"width":119,"height":123,"rx":58,"fill":124,"stroke":125,"style":84},"154",[68,141,143],{"x":128,"y":142,"style":130,"fill":73,"textAnchor":74},"181.78","tile_02.tif",[68,145,136],{"x":128,"y":146,"style":135,"fill":125,"textAnchor":74},"203.78",[110,148],{"x1":149,"y1":113,"x2":150,"y2":113,"stroke":66,"style":116},"500","542",[44,152],{"x":153,"y":154,"width":155,"height":156,"rx":58,"fill":157,"stroke":158,"style":84},"546","100","190","90","#e8efe6","#15803d",[68,160,164],{"x":161,"y":162,"style":130,"fill":163,"textAnchor":74},"641","135.78","#166534","QGIS sees",[68,166,168],{"x":161,"y":167,"style":135,"fill":66,"textAnchor":74},"161.78","one raster",[170,171,173],"h2",{"id":172},"prerequisites","Prerequisites",[175,176,177,181],"ul",{},[178,179,180],"li",{},"QGIS 3.34 LTR or newer, or the QGIS 4 series, with the GDAL Processing provider.",[178,182,183,184,188],{},"Source rasters in the same CRS. A mosaic VRT cannot reproject; reproject first, or use ",[185,186,187],"code",{},"gdal:warpreproject"," to write a warped VRT.",[170,190,192],{"id":191},"build-a-mosaic-vrt","Build a mosaic VRT",[14,194,195,196,199],{},"The most common VRT stitches tiles into one seamless raster. ",[185,197,198],{},"gdal:buildvirtualraster"," takes a list of inputs and writes the XML.",[201,202,207],"pre",{"className":203,"code":204,"language":205,"meta":206,"style":206},"language-python shiki shiki-themes github-dark","from pathlib import Path\nimport processing\nfrom qgis.core import QgsRasterLayer, QgsProject\n\ntiles = sorted(str(p) for p in Path(\"\u002Fdata\u002Fdem\u002Ftiles\").glob(\"dem_*.tif\"))\nvrt = processing.run(\"gdal:buildvirtualraster\", {\n    \"INPUT\": tiles,\n    \"RESOLUTION\": 0,             # 0 = average, 1 = highest, 2 = lowest\n    \"SEPARATE\": False,\n    \"PROJ_DIFFERENCE\": False,\n    \"ADD_ALPHA\": False,\n    \"ASSIGN_CRS\": None,\n    \"RESAMPLING\": 0,\n    \"SRC_NODATA\": \"-9999\",\n    \"OUTPUT\": \"\u002Fdata\u002Fdem\u002Fdem_all.vrt\",\n})[\"OUTPUT\"]\n\ndem = QgsRasterLayer(vrt, \"DEM (all tiles)\")\nprint(dem.isValid(), dem.width(), \"x\", dem.height(), dem.crs().authid())\nQgsProject.instance().addMapLayer(dem)\n","python","",[185,208,209,227,235,248,255,302,319,328,346,360,372,384,397,409,422,435,447,452,469,484],{"__ignoreMap":206},[210,211,213,217,221,224],"span",{"class":110,"line":212},1,[210,214,216],{"class":215},"snl16","from",[210,218,220],{"class":219},"s95oV"," pathlib ",[210,222,223],{"class":215},"import",[210,225,226],{"class":219}," Path\n",[210,228,230,232],{"class":110,"line":229},2,[210,231,223],{"class":215},[210,233,234],{"class":219}," processing\n",[210,236,238,240,243,245],{"class":110,"line":237},3,[210,239,216],{"class":215},[210,241,242],{"class":219}," qgis.core ",[210,244,223],{"class":215},[210,246,247],{"class":219}," QgsRasterLayer, QgsProject\n",[210,249,251],{"class":110,"line":250},4,[210,252,254],{"emptyLinePlaceholder":253},true,"\n",[210,256,258,261,264,268,271,274,277,280,283,286,289,293,296,299],{"class":110,"line":257},5,[210,259,260],{"class":219},"tiles ",[210,262,263],{"class":215},"=",[210,265,267],{"class":266},"sDLfK"," sorted",[210,269,270],{"class":219},"(",[210,272,273],{"class":266},"str",[210,275,276],{"class":219},"(p) ",[210,278,279],{"class":215},"for",[210,281,282],{"class":219}," p ",[210,284,285],{"class":215},"in",[210,287,288],{"class":219}," Path(",[210,290,292],{"class":291},"sU2Wk","\"\u002Fdata\u002Fdem\u002Ftiles\"",[210,294,295],{"class":219},").glob(",[210,297,298],{"class":291},"\"dem_*.tif\"",[210,300,301],{"class":219},"))\n",[210,303,305,308,310,313,316],{"class":110,"line":304},6,[210,306,307],{"class":219},"vrt ",[210,309,263],{"class":215},[210,311,312],{"class":219}," processing.run(",[210,314,315],{"class":291},"\"gdal:buildvirtualraster\"",[210,317,318],{"class":219},", {\n",[210,320,322,325],{"class":110,"line":321},7,[210,323,324],{"class":291},"    \"INPUT\"",[210,326,327],{"class":219},": tiles,\n",[210,329,331,334,337,339,342],{"class":110,"line":330},8,[210,332,333],{"class":291},"    \"RESOLUTION\"",[210,335,336],{"class":219},": ",[210,338,46],{"class":266},[210,340,341],{"class":219},",             ",[210,343,345],{"class":344},"sjoCn","# 0 = average, 1 = highest, 2 = lowest\n",[210,347,349,352,354,357],{"class":110,"line":348},9,[210,350,351],{"class":291},"    \"SEPARATE\"",[210,353,336],{"class":219},[210,355,356],{"class":266},"False",[210,358,359],{"class":219},",\n",[210,361,363,366,368,370],{"class":110,"line":362},10,[210,364,365],{"class":291},"    \"PROJ_DIFFERENCE\"",[210,367,336],{"class":219},[210,369,356],{"class":266},[210,371,359],{"class":219},[210,373,375,378,380,382],{"class":110,"line":374},11,[210,376,377],{"class":291},"    \"ADD_ALPHA\"",[210,379,336],{"class":219},[210,381,356],{"class":266},[210,383,359],{"class":219},[210,385,387,390,392,395],{"class":110,"line":386},12,[210,388,389],{"class":291},"    \"ASSIGN_CRS\"",[210,391,336],{"class":219},[210,393,394],{"class":266},"None",[210,396,359],{"class":219},[210,398,400,403,405,407],{"class":110,"line":399},13,[210,401,402],{"class":291},"    \"RESAMPLING\"",[210,404,336],{"class":219},[210,406,46],{"class":266},[210,408,359],{"class":219},[210,410,412,415,417,420],{"class":110,"line":411},14,[210,413,414],{"class":291},"    \"SRC_NODATA\"",[210,416,336],{"class":219},[210,418,419],{"class":291},"\"-9999\"",[210,421,359],{"class":219},[210,423,425,428,430,433],{"class":110,"line":424},15,[210,426,427],{"class":291},"    \"OUTPUT\"",[210,429,336],{"class":219},[210,431,432],{"class":291},"\"\u002Fdata\u002Fdem\u002Fdem_all.vrt\"",[210,434,359],{"class":219},[210,436,438,441,444],{"class":110,"line":437},16,[210,439,440],{"class":219},"})[",[210,442,443],{"class":291},"\"OUTPUT\"",[210,445,446],{"class":219},"]\n",[210,448,450],{"class":110,"line":449},17,[210,451,254],{"emptyLinePlaceholder":253},[210,453,455,458,460,463,466],{"class":110,"line":454},18,[210,456,457],{"class":219},"dem ",[210,459,263],{"class":215},[210,461,462],{"class":219}," QgsRasterLayer(vrt, ",[210,464,465],{"class":291},"\"DEM (all tiles)\"",[210,467,468],{"class":219},")\n",[210,470,472,475,478,481],{"class":110,"line":471},19,[210,473,474],{"class":266},"print",[210,476,477],{"class":219},"(dem.isValid(), dem.width(), ",[210,479,480],{"class":291},"\"x\"",[210,482,483],{"class":219},", dem.height(), dem.crs().authid())\n",[210,485,487],{"class":110,"line":486},20,[210,488,489],{"class":219},"QgsProject.instance().addMapLayer(dem)\n",[14,491,492,496,497,500,501,504,505,508,509,512],{},[493,494,495],"strong",{},"Breakdown:"," With ",[185,498,499],{},"SEPARATE"," false, all inputs go into the same band, placed by their georeferencing — a mosaic. ",[185,502,503],{},"RESOLUTION"," decides the VRT's pixel size when tiles differ; for uniform tiles it makes no difference, and for mixed tiles \"highest\" keeps detail at the cost of resampling coarse tiles. ",[185,506,507],{},"SRC_NODATA"," declares which source value is empty, so gaps and tile edges stay transparent and later tiles do not overwrite earlier ones with empty pixels. ",[185,510,511],{},"PROJ_DIFFERENCE"," false makes the build refuse tiles in a different CRS, which is safer than silently mixing them. The result opens instantly regardless of how many gigabytes the tiles hold.",[170,514,516],{"id":515},"stack-bands-from-separate-files","Stack bands from separate files",[14,518,519,520,522],{},"Many satellite products deliver each band as its own file. A VRT with ",[185,521,499],{}," set puts each input in its own band, producing a multiband image without copying anything.",[14,524,525],{},[29,526,529,532,535,538,541,546,551,555,559,563,568,572,575,578],{"viewBox":527,"role":32,"ariaLabel":528,"xmlns":34},"0 0 760 222","SEPARATE false places inputs side by side as a mosaic in one band, while SEPARATE true stacks them as separate bands of one grid",[36,530,531],{},"Mosaic versus band stack",[40,533,534],{},"With SEPARATE false, inputs are placed side by side in one band according to their location, forming a mosaic. With SEPARATE true, each input becomes a band of the same grid, forming a stack such as red, green, blue and near-infrared from four single-band files. Inputs for a stack must cover the same area at the same resolution.",[44,536],{"x":46,"y":46,"width":47,"height":537,"fill":49},"222",[68,539,540],{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"Side by side, or on top of each other",[44,542],{"x":78,"y":543,"width":544,"height":545,"rx":58,"fill":82,"stroke":83,"style":84},"56","340","150",[68,547,550],{"x":548,"y":549,"style":130,"fill":83,"textAnchor":74},"194","92.78","SEPARATE = False",[68,552,554],{"x":548,"y":553,"style":135,"fill":66,"textAnchor":74},"120.78","tiles side by side",[68,556,558],{"x":548,"y":557,"style":135,"fill":66,"textAnchor":74},"148.78","one band",[68,560,562],{"x":548,"y":561,"style":135,"fill":125,"textAnchor":74},"176.78","mosaic",[44,564],{"x":565,"y":543,"width":544,"height":545,"rx":58,"fill":566,"stroke":567,"style":84},"396","#eff3ff","#2563eb",[68,569,571],{"x":570,"y":549,"style":130,"fill":567,"textAnchor":74},"566","SEPARATE = True",[68,573,574],{"x":570,"y":553,"style":135,"fill":66,"textAnchor":74},"files on top",[68,576,577],{"x":570,"y":557,"style":135,"fill":66,"textAnchor":74},"one band per file",[68,579,580],{"x":570,"y":561,"style":135,"fill":125,"textAnchor":74},"band stack",[201,582,584],{"className":203,"code":583,"language":205,"meta":206,"style":206},"scene = Path(\"\u002Fdata\u002Fs2\u002FS2B_20260614_T33UUU\")\nbands = [str(scene \u002F f\"B{b}_10m.jp2\") for b in (\"04\", \"03\", \"02\", \"08\")]   # R, G, B, NIR\n\nstack = processing.run(\"gdal:buildvirtualraster\", {\n    \"INPUT\": bands, \"SEPARATE\": True, \"RESOLUTION\": 1,\n    \"SRC_NODATA\": \"0\", \"OUTPUT\": str(scene \u002F \"rgbn.vrt\")})[\"OUTPUT\"]\n\nimg = QgsRasterLayer(stack, \"Sentinel-2 RGBN\")\nprint(img.bandCount(), \"bands\")\n",[185,585,586,600,674,678,691,718,749,753,768],{"__ignoreMap":206},[210,587,588,591,593,595,598],{"class":110,"line":212},[210,589,590],{"class":219},"scene ",[210,592,263],{"class":215},[210,594,288],{"class":219},[210,596,597],{"class":291},"\"\u002Fdata\u002Fs2\u002FS2B_20260614_T33UUU\"",[210,599,468],{"class":219},[210,601,602,605,607,610,612,615,618,621,624,627,630,633,636,639,641,644,646,649,652,655,658,660,663,665,668,671],{"class":110,"line":229},[210,603,604],{"class":219},"bands ",[210,606,263],{"class":215},[210,608,609],{"class":219}," [",[210,611,273],{"class":266},[210,613,614],{"class":219},"(scene ",[210,616,617],{"class":215},"\u002F",[210,619,620],{"class":215}," f",[210,622,623],{"class":291},"\"B",[210,625,626],{"class":266},"{",[210,628,629],{"class":219},"b",[210,631,632],{"class":266},"}",[210,634,635],{"class":291},"_10m.jp2\"",[210,637,638],{"class":219},") ",[210,640,279],{"class":215},[210,642,643],{"class":219}," b ",[210,645,285],{"class":215},[210,647,648],{"class":219}," (",[210,650,651],{"class":291},"\"04\"",[210,653,654],{"class":219},", ",[210,656,657],{"class":291},"\"03\"",[210,659,654],{"class":219},[210,661,662],{"class":291},"\"02\"",[210,664,654],{"class":219},[210,666,667],{"class":291},"\"08\"",[210,669,670],{"class":219},")]   ",[210,672,673],{"class":344},"# R, G, B, NIR\n",[210,675,676],{"class":110,"line":237},[210,677,254],{"emptyLinePlaceholder":253},[210,679,680,683,685,687,689],{"class":110,"line":250},[210,681,682],{"class":219},"stack ",[210,684,263],{"class":215},[210,686,312],{"class":219},[210,688,315],{"class":291},[210,690,318],{"class":219},[210,692,693,695,698,701,703,706,708,711,713,716],{"class":110,"line":257},[210,694,324],{"class":291},[210,696,697],{"class":219},": bands, ",[210,699,700],{"class":291},"\"SEPARATE\"",[210,702,336],{"class":219},[210,704,705],{"class":266},"True",[210,707,654],{"class":219},[210,709,710],{"class":291},"\"RESOLUTION\"",[210,712,336],{"class":219},[210,714,715],{"class":266},"1",[210,717,359],{"class":219},[210,719,720,722,724,727,729,731,733,735,737,739,742,745,747],{"class":110,"line":304},[210,721,414],{"class":291},[210,723,336],{"class":219},[210,725,726],{"class":291},"\"0\"",[210,728,654],{"class":219},[210,730,443],{"class":291},[210,732,336],{"class":219},[210,734,273],{"class":266},[210,736,614],{"class":219},[210,738,617],{"class":215},[210,740,741],{"class":291}," \"rgbn.vrt\"",[210,743,744],{"class":219},")})[",[210,746,443],{"class":291},[210,748,446],{"class":219},[210,750,751],{"class":110,"line":321},[210,752,254],{"emptyLinePlaceholder":253},[210,754,755,758,760,763,766],{"class":110,"line":330},[210,756,757],{"class":219},"img ",[210,759,263],{"class":215},[210,761,762],{"class":219}," QgsRasterLayer(stack, ",[210,764,765],{"class":291},"\"Sentinel-2 RGBN\"",[210,767,468],{"class":219},[210,769,770,772,775,778],{"class":110,"line":348},[210,771,474],{"class":266},[210,773,774],{"class":219},"(img.bandCount(), ",[210,776,777],{"class":291},"\"bands\"",[210,779,468],{"class":219},[14,781,782,784,785,788,789,792,793,797],{},[493,783,495],{}," Band order follows the input list, so listing red, green, blue, near-infrared gives a stack where bands 1–3 render as a natural-colour image by default. All inputs should share extent and resolution; if they differ — Sentinel-2's 20 m bands next to 10 m ones — ",[185,786,787],{},"RESOLUTION: 1"," resamples to the finest. Band names can be added by editing the ",[185,790,791],{},"Description"," element of each band in the VRT XML, which makes the stack self-documenting in QGIS's layer properties. The stacked VRT can feed index calculations, as in ",[21,794,796],{"href":795},"\u002Fspatial-data-processing-automation\u002Fpyqgis-and-the-python-data-stack\u002Fuse-rasterio-and-numpy-with-qgis-rasters\u002F","using rasterio and NumPy with QGIS rasters",".",[170,799,801],{"id":800},"keep-paths-portable","Keep paths portable",[14,803,804],{},"A VRT stores paths to its sources. Absolute paths break when the folder moves or is shared; relative paths keep the VRT working wherever the folder goes, as long as the VRT moves with its sources.",[201,806,808],{"className":203,"code":807,"language":205,"meta":206,"style":206},"import xml.etree.ElementTree as ET\nimport os\n\ndef make_relative(vrt_path):\n    tree = ET.parse(vrt_path)\n    base = os.path.dirname(os.path.abspath(vrt_path))\n    changed = 0\n    for src in tree.iter(\"SourceFilename\"):\n        if src.get(\"relativeToVRT\") == \"0\" and os.path.isabs(src.text):\n            src.text = os.path.relpath(src.text, base)\n            src.set(\"relativeToVRT\", \"1\")\n            changed += 1\n    tree.write(vrt_path)\n    return changed\n\nprint(make_relative(\"\u002Fdata\u002Fdem\u002Fdem_all.vrt\"), \"paths made relative\")\n",[185,809,810,823,830,834,846,859,869,879,898,923,933,947,958,963,971,975],{"__ignoreMap":206},[210,811,812,814,817,820],{"class":110,"line":212},[210,813,223],{"class":215},[210,815,816],{"class":219}," xml.etree.ElementTree ",[210,818,819],{"class":215},"as",[210,821,822],{"class":266}," ET\n",[210,824,825,827],{"class":110,"line":229},[210,826,223],{"class":215},[210,828,829],{"class":219}," os\n",[210,831,832],{"class":110,"line":237},[210,833,254],{"emptyLinePlaceholder":253},[210,835,836,839,843],{"class":110,"line":250},[210,837,838],{"class":215},"def",[210,840,842],{"class":841},"svObZ"," make_relative",[210,844,845],{"class":219},"(vrt_path):\n",[210,847,848,851,853,856],{"class":110,"line":257},[210,849,850],{"class":219},"    tree ",[210,852,263],{"class":215},[210,854,855],{"class":266}," ET",[210,857,858],{"class":219},".parse(vrt_path)\n",[210,860,861,864,866],{"class":110,"line":304},[210,862,863],{"class":219},"    base ",[210,865,263],{"class":215},[210,867,868],{"class":219}," os.path.dirname(os.path.abspath(vrt_path))\n",[210,870,871,874,876],{"class":110,"line":321},[210,872,873],{"class":219},"    changed ",[210,875,263],{"class":215},[210,877,878],{"class":266}," 0\n",[210,880,881,884,887,889,892,895],{"class":110,"line":330},[210,882,883],{"class":215},"    for",[210,885,886],{"class":219}," src ",[210,888,285],{"class":215},[210,890,891],{"class":219}," tree.iter(",[210,893,894],{"class":291},"\"SourceFilename\"",[210,896,897],{"class":219},"):\n",[210,899,900,903,906,909,911,914,917,920],{"class":110,"line":348},[210,901,902],{"class":215},"        if",[210,904,905],{"class":219}," src.get(",[210,907,908],{"class":291},"\"relativeToVRT\"",[210,910,638],{"class":219},[210,912,913],{"class":215},"==",[210,915,916],{"class":291}," \"0\"",[210,918,919],{"class":215}," and",[210,921,922],{"class":219}," os.path.isabs(src.text):\n",[210,924,925,928,930],{"class":110,"line":362},[210,926,927],{"class":219},"            src.text ",[210,929,263],{"class":215},[210,931,932],{"class":219}," os.path.relpath(src.text, base)\n",[210,934,935,938,940,942,945],{"class":110,"line":374},[210,936,937],{"class":219},"            src.set(",[210,939,908],{"class":291},[210,941,654],{"class":219},[210,943,944],{"class":291},"\"1\"",[210,946,468],{"class":219},[210,948,949,952,955],{"class":110,"line":386},[210,950,951],{"class":219},"            changed ",[210,953,954],{"class":215},"+=",[210,956,957],{"class":266}," 1\n",[210,959,960],{"class":110,"line":399},[210,961,962],{"class":219},"    tree.write(vrt_path)\n",[210,964,965,968],{"class":110,"line":411},[210,966,967],{"class":215},"    return",[210,969,970],{"class":219}," changed\n",[210,972,973],{"class":110,"line":424},[210,974,254],{"emptyLinePlaceholder":253},[210,976,977,979,982,984,987,990],{"class":110,"line":437},[210,978,474],{"class":266},[210,980,981],{"class":219},"(make_relative(",[210,983,432],{"class":291},[210,985,986],{"class":219},"), ",[210,988,989],{"class":291},"\"paths made relative\"",[210,991,468],{"class":219},[14,993,994,996,997,1000,1001,1004],{},[493,995,495],{}," Each source in the VRT XML is a ",[185,998,999],{},"SourceFilename"," element with a ",[185,1002,1003],{},"relativeToVRT"," flag. Rewriting absolute paths relative to the VRT's own folder and setting the flag makes the VRT portable. GDAL writes relative paths automatically when the sources are below the VRT's folder, so placing the VRT next to or above the tile folder avoids the need for this fix. A VRT referencing files on a network share by drive letter is the classic case that breaks for colleagues — relative paths solve it.",[170,1006,1008],{"id":1007},"add-overviews-for-fast-display","Add overviews for fast display",[14,1010,1011,1012,1015],{},"A VRT over hundreds of tiles must open every tile to draw a zoomed-out view, which is slow. Overviews — pre-computed lower-resolution copies — make it fast. For a VRT they are stored in a sidecar ",[185,1013,1014],{},".ovr"," file.",[14,1017,1018],{},[29,1019,1022,1025,1028,1031,1038,1041,1046,1050,1054,1058,1062,1067,1071,1074,1077,1081,1085,1090],{"viewBox":1020,"role":32,"ariaLabel":1021,"xmlns":34},"0 0 760 196","Overview levels stored in a sidecar file let a zoomed-out VRT view read small images instead of every full-resolution tile",[36,1023,1024],{},"Overviews for a VRT",[40,1026,1027],{},"Without overviews, a zoomed-out view of a national VRT reads every tile at full resolution and is slow. Building overviews writes reduced-resolution levels, for example 2, 4, 8, 16 and 32, into a sidecar ovr file. QGIS then reads the appropriate level when zoomed out, and the full-resolution tiles only when zoomed in.",[44,1029],{"x":46,"y":46,"width":47,"height":1030,"fill":49},"196",[51,1032,1033],{},[54,1034,1036],{"id":1035,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"vrtOvrArrow",[63,1037],{"d":65,"fill":66},[68,1039,1040],{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"Zoomed out: read the small copies",[44,1042],{"x":78,"y":79,"width":537,"height":1043,"rx":58,"fill":1044,"stroke":1045,"style":84},"120","#fdf2e2","#b91c1c",[68,1047,1049],{"x":1048,"y":129,"style":130,"fill":1045,"textAnchor":74},"135","no overviews",[68,1051,1053],{"x":1048,"y":1052,"style":135,"fill":66,"textAnchor":74},"123.78","every tile read",[68,1055,1057],{"x":1048,"y":1056,"style":135,"fill":125,"textAnchor":74},"149.78","slow when zoomed out",[110,1059],{"x1":48,"y1":1043,"x2":1060,"y2":1043,"stroke":66,"style":1061},"288","stroke-width:1.8;marker-end:url(#vrtOvrArrow)",[44,1063],{"x":1064,"y":79,"width":1065,"height":1043,"rx":58,"fill":1044,"stroke":1066,"style":84},"292","200","#b45309",[68,1068,1070],{"x":1069,"y":129,"style":130,"fill":1066,"textAnchor":74},"392","gdal:overviews",[68,1072,1073],{"x":1069,"y":1052,"style":135,"fill":66,"textAnchor":74},"2 · 4 · 8 · 16 · 32",[68,1075,1076],{"x":1069,"y":1056,"style":135,"fill":125,"textAnchor":74},"sidecar .ovr",[110,1078],{"x1":1079,"y1":1043,"x2":1080,"y2":1043,"stroke":66,"style":1061},"492","534",[44,1082],{"x":1083,"y":79,"width":1084,"height":1043,"rx":58,"fill":157,"stroke":158,"style":84},"538","198",[68,1086,1089],{"x":1087,"y":1088,"style":130,"fill":163,"textAnchor":74},"637","110.78","fast at",[68,1091,1093],{"x":1087,"y":1092,"style":135,"fill":66,"textAnchor":74},"136.78","every scale",[201,1095,1097],{"className":203,"code":1096,"language":205,"meta":206,"style":206},"processing.run(\"gdal:overviews\", {\n    \"INPUT\": \"\u002Fdata\u002Fdem\u002Fdem_all.vrt\",\n    \"CLEAN\": False,\n    \"LEVELS\": \"2 4 8 16 32\",\n    \"RESAMPLING\": 1,                 # average\n    \"FORMAT\": 1,                     # external .ovr\n    \"EXTRA\": \"--config COMPRESS_OVERVIEW DEFLATE\",\n})\n",[185,1098,1099,1109,1119,1130,1142,1156,1171,1183],{"__ignoreMap":206},[210,1100,1101,1104,1107],{"class":110,"line":212},[210,1102,1103],{"class":219},"processing.run(",[210,1105,1106],{"class":291},"\"gdal:overviews\"",[210,1108,318],{"class":219},[210,1110,1111,1113,1115,1117],{"class":110,"line":229},[210,1112,324],{"class":291},[210,1114,336],{"class":219},[210,1116,432],{"class":291},[210,1118,359],{"class":219},[210,1120,1121,1124,1126,1128],{"class":110,"line":237},[210,1122,1123],{"class":291},"    \"CLEAN\"",[210,1125,336],{"class":219},[210,1127,356],{"class":266},[210,1129,359],{"class":219},[210,1131,1132,1135,1137,1140],{"class":110,"line":250},[210,1133,1134],{"class":291},"    \"LEVELS\"",[210,1136,336],{"class":219},[210,1138,1139],{"class":291},"\"2 4 8 16 32\"",[210,1141,359],{"class":219},[210,1143,1144,1146,1148,1150,1153],{"class":110,"line":257},[210,1145,402],{"class":291},[210,1147,336],{"class":219},[210,1149,715],{"class":266},[210,1151,1152],{"class":219},",                 ",[210,1154,1155],{"class":344},"# average\n",[210,1157,1158,1161,1163,1165,1168],{"class":110,"line":304},[210,1159,1160],{"class":291},"    \"FORMAT\"",[210,1162,336],{"class":219},[210,1164,715],{"class":266},[210,1166,1167],{"class":219},",                     ",[210,1169,1170],{"class":344},"# external .ovr\n",[210,1172,1173,1176,1178,1181],{"class":110,"line":321},[210,1174,1175],{"class":291},"    \"EXTRA\"",[210,1177,336],{"class":219},[210,1179,1180],{"class":291},"\"--config COMPRESS_OVERVIEW DEFLATE\"",[210,1182,359],{"class":219},[210,1184,1185],{"class":110,"line":330},[210,1186,1187],{"class":219},"})\n",[14,1189,1190,1192],{},[493,1191,495],{}," Levels 2 to 32 cover typical zoom ranges for a national DEM; add 64 or 128 for very large mosaics. Averaging is right for continuous data like elevation; use nearest neighbour for categorical rasters such as land cover, where averaging class codes produces meaningless values. External overviews leave the source tiles untouched. Rebuild overviews when tiles change, since they are not updated automatically.",[170,1194,1196],{"id":1195},"build-a-vrt-for-one-area-from-a-tile-index","Build a VRT for one area from a tile index",[14,1198,1199],{},"For a national tile set, building one VRT over everything is fine for display, but analyses of a single catchment run faster against a VRT of just the tiles they need. A tile index — a polygon layer with one footprint per file — makes selecting them a spatial query.",[201,1201,1203],{"className":203,"code":1202,"language":205,"meta":206,"style":206},"index = processing.run(\"gdal:tileindex\", {\n    \"LAYERS\": tiles, \"PATH_FIELD_NAME\": \"location\", \"ABSOLUTE_PATH\": True,\n    \"OUTPUT\": \"\u002Fdata\u002Fdem\u002Fdem_tiles_index.gpkg\"})[\"OUTPUT\"]\nindex_layer = QgsVectorLayer(index, \"tile index\", \"ogr\")\n\ncatchment = QgsProject.instance().mapLayersByName(\"catchment\")[0]\npicked = processing.run(\"native:extractbylocation\", {\n    \"INPUT\": index_layer, \"PREDICATE\": [0], \"INTERSECT\": catchment,\n    \"OUTPUT\": \"memory:\"})[\"OUTPUT\"]\npaths = [f[\"location\"] for f in picked.getFeatures()]\n\nlocal = processing.run(\"gdal:buildvirtualraster\", {\n    \"INPUT\": paths, \"SEPARATE\": False, \"SRC_NODATA\": \"-9999\",\n    \"OUTPUT\": \"\u002Fdata\u002Fdem\u002Fdem_catchment.vrt\"})[\"OUTPUT\"]\nprint(len(paths), \"tiles in the catchment VRT\")\n",[185,1204,1205,1219,1246,1261,1281,1285,1305,1319,1343,1358,1383,1387,1400,1424,1439],{"__ignoreMap":206},[210,1206,1207,1210,1212,1214,1217],{"class":110,"line":212},[210,1208,1209],{"class":219},"index ",[210,1211,263],{"class":215},[210,1213,312],{"class":219},[210,1215,1216],{"class":291},"\"gdal:tileindex\"",[210,1218,318],{"class":219},[210,1220,1221,1224,1227,1230,1232,1235,1237,1240,1242,1244],{"class":110,"line":229},[210,1222,1223],{"class":291},"    \"LAYERS\"",[210,1225,1226],{"class":219},": tiles, ",[210,1228,1229],{"class":291},"\"PATH_FIELD_NAME\"",[210,1231,336],{"class":219},[210,1233,1234],{"class":291},"\"location\"",[210,1236,654],{"class":219},[210,1238,1239],{"class":291},"\"ABSOLUTE_PATH\"",[210,1241,336],{"class":219},[210,1243,705],{"class":266},[210,1245,359],{"class":219},[210,1247,1248,1250,1252,1255,1257,1259],{"class":110,"line":237},[210,1249,427],{"class":291},[210,1251,336],{"class":219},[210,1253,1254],{"class":291},"\"\u002Fdata\u002Fdem\u002Fdem_tiles_index.gpkg\"",[210,1256,440],{"class":219},[210,1258,443],{"class":291},[210,1260,446],{"class":219},[210,1262,1263,1266,1268,1271,1274,1276,1279],{"class":110,"line":250},[210,1264,1265],{"class":219},"index_layer ",[210,1267,263],{"class":215},[210,1269,1270],{"class":219}," QgsVectorLayer(index, ",[210,1272,1273],{"class":291},"\"tile index\"",[210,1275,654],{"class":219},[210,1277,1278],{"class":291},"\"ogr\"",[210,1280,468],{"class":219},[210,1282,1283],{"class":110,"line":257},[210,1284,254],{"emptyLinePlaceholder":253},[210,1286,1287,1290,1292,1295,1298,1301,1303],{"class":110,"line":304},[210,1288,1289],{"class":219},"catchment ",[210,1291,263],{"class":215},[210,1293,1294],{"class":219}," QgsProject.instance().mapLayersByName(",[210,1296,1297],{"class":291},"\"catchment\"",[210,1299,1300],{"class":219},")[",[210,1302,46],{"class":266},[210,1304,446],{"class":219},[210,1306,1307,1310,1312,1314,1317],{"class":110,"line":321},[210,1308,1309],{"class":219},"picked ",[210,1311,263],{"class":215},[210,1313,312],{"class":219},[210,1315,1316],{"class":291},"\"native:extractbylocation\"",[210,1318,318],{"class":219},[210,1320,1321,1323,1326,1329,1332,1334,1337,1340],{"class":110,"line":330},[210,1322,324],{"class":291},[210,1324,1325],{"class":219},": index_layer, ",[210,1327,1328],{"class":291},"\"PREDICATE\"",[210,1330,1331],{"class":219},": [",[210,1333,46],{"class":266},[210,1335,1336],{"class":219},"], ",[210,1338,1339],{"class":291},"\"INTERSECT\"",[210,1341,1342],{"class":219},": catchment,\n",[210,1344,1345,1347,1349,1352,1354,1356],{"class":110,"line":348},[210,1346,427],{"class":291},[210,1348,336],{"class":219},[210,1350,1351],{"class":291},"\"memory:\"",[210,1353,440],{"class":219},[210,1355,443],{"class":291},[210,1357,446],{"class":219},[210,1359,1360,1363,1365,1368,1370,1373,1375,1378,1380],{"class":110,"line":362},[210,1361,1362],{"class":219},"paths ",[210,1364,263],{"class":215},[210,1366,1367],{"class":219}," [f[",[210,1369,1234],{"class":291},[210,1371,1372],{"class":219},"] ",[210,1374,279],{"class":215},[210,1376,1377],{"class":219}," f ",[210,1379,285],{"class":215},[210,1381,1382],{"class":219}," picked.getFeatures()]\n",[210,1384,1385],{"class":110,"line":374},[210,1386,254],{"emptyLinePlaceholder":253},[210,1388,1389,1392,1394,1396,1398],{"class":110,"line":386},[210,1390,1391],{"class":219},"local ",[210,1393,263],{"class":215},[210,1395,312],{"class":219},[210,1397,315],{"class":291},[210,1399,318],{"class":219},[210,1401,1402,1404,1407,1409,1411,1413,1415,1418,1420,1422],{"class":110,"line":399},[210,1403,324],{"class":291},[210,1405,1406],{"class":219},": paths, ",[210,1408,700],{"class":291},[210,1410,336],{"class":219},[210,1412,356],{"class":266},[210,1414,654],{"class":219},[210,1416,1417],{"class":291},"\"SRC_NODATA\"",[210,1419,336],{"class":219},[210,1421,419],{"class":291},[210,1423,359],{"class":219},[210,1425,1426,1428,1430,1433,1435,1437],{"class":110,"line":411},[210,1427,427],{"class":291},[210,1429,336],{"class":219},[210,1431,1432],{"class":291},"\"\u002Fdata\u002Fdem\u002Fdem_catchment.vrt\"",[210,1434,440],{"class":219},[210,1436,443],{"class":291},[210,1438,446],{"class":219},[210,1440,1441,1443,1445,1448,1451,1454],{"class":110,"line":424},[210,1442,474],{"class":266},[210,1444,270],{"class":219},[210,1446,1447],{"class":266},"len",[210,1449,1450],{"class":219},"(paths), ",[210,1452,1453],{"class":291},"\"tiles in the catchment VRT\"",[210,1455,468],{"class":219},[14,1457,1458,1460,1461,1464,1465,1468],{},[493,1459,495],{}," ",[185,1462,1463],{},"gdal:tileindex"," writes one polygon per raster with its path in a field, and it only needs rebuilding when tiles are added. Selecting footprints that intersect the catchment returns exactly the files to include, and a VRT over those is small, fast to open and fast to process. The tile index is useful in its own right — styled over a basemap it shows coverage and gaps at a glance. Add ",[185,1466,1467],{},"from qgis.core import QgsVectorLayer"," when running this section alone.",[170,1470,1472],{"id":1471},"convert-a-vrt-into-a-real-file","Convert a VRT into a real file",[14,1474,1475,1476,1479],{},"Sometimes a standalone file is needed after all: to send to someone, to archive, or for a tool that does not read VRTs. ",[185,1477,1478],{},"gdal:translate"," reads the VRT and writes a GeoTIFF in one step.",[201,1481,1483],{"className":203,"code":1482,"language":205,"meta":206,"style":206},"processing.run(\"gdal:translate\", {\n    \"INPUT\": \"\u002Fdata\u002Fdem\u002Fdem_all.vrt\",\n    \"OPTIONS\": \"COMPRESS=DEFLATE|PREDICTOR=3|TILED=YES|BIGTIFF=IF_SAFER\",\n    \"DATA_TYPE\": 0,                  # keep input type\n    \"OUTPUT\": \"\u002Fdata\u002Fdelivery\u002Fdem_mosaic.tif\",\n})\n",[185,1484,1485,1494,1504,1516,1531,1542],{"__ignoreMap":206},[210,1486,1487,1489,1492],{"class":110,"line":212},[210,1488,1103],{"class":219},[210,1490,1491],{"class":291},"\"gdal:translate\"",[210,1493,318],{"class":219},[210,1495,1496,1498,1500,1502],{"class":110,"line":229},[210,1497,324],{"class":291},[210,1499,336],{"class":219},[210,1501,432],{"class":291},[210,1503,359],{"class":219},[210,1505,1506,1509,1511,1514],{"class":110,"line":237},[210,1507,1508],{"class":291},"    \"OPTIONS\"",[210,1510,336],{"class":219},[210,1512,1513],{"class":291},"\"COMPRESS=DEFLATE|PREDICTOR=3|TILED=YES|BIGTIFF=IF_SAFER\"",[210,1515,359],{"class":219},[210,1517,1518,1521,1523,1525,1528],{"class":110,"line":250},[210,1519,1520],{"class":291},"    \"DATA_TYPE\"",[210,1522,336],{"class":219},[210,1524,46],{"class":266},[210,1526,1527],{"class":219},",                  ",[210,1529,1530],{"class":344},"# keep input type\n",[210,1532,1533,1535,1537,1540],{"class":110,"line":257},[210,1534,427],{"class":291},[210,1536,336],{"class":219},[210,1538,1539],{"class":291},"\"\u002Fdata\u002Fdelivery\u002Fdem_mosaic.tif\"",[210,1541,359],{"class":219},[210,1543,1544],{"class":110,"line":304},[210,1545,1187],{"class":219},[14,1547,1548,1550,1551,1554,1555,1559,1560,797],{},[493,1549,495],{}," Translating a VRT is equivalent to merging the tiles, with the advantage that you have already inspected the VRT in QGIS and know the result is right. Combine it with a ",[185,1552,1553],{},"PROJWIN"," extent to export just a study area. For cloud delivery, write a Cloud Optimized GeoTIFF instead, as in ",[21,1556,1558],{"href":1557},"\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fexport-cloud-optimized-geotiff-pyqgis\u002F","exporting a COG",". The merge route is compared in ",[21,1561,1563],{"href":1562},"\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fmerge-raster-tiles-into-mosaic-pyqgis\u002F","merging raster tiles into a mosaic",[170,1565,1567],{"id":1566},"qgis-version-compatibility","QGIS version compatibility",[14,1569,1570,654,1572,1574,1575,1577,1578,1581],{},[185,1571,198],{},[185,1573,1070],{}," and ",[185,1576,1478],{}," work on QGIS 3.34 LTR, 3.40 LTR and QGIS 4. Parameter names for source NoData and resampling have been stable across these releases; check ",[185,1579,1580],{},"processing.algorithmHelp(...)"," for the enum orders on your version. VRT reading behaviour depends on the bundled GDAL, which is 3.8 or newer on current releases.",[170,1583,1585],{"id":1584},"troubleshooting","Troubleshooting",[175,1587,1588,1594,1600,1606],{},[178,1589,1590,1593],{},[493,1591,1592],{},"The VRT opens but shows nothing."," Source paths are wrong, usually absolute paths after a move; make them relative.",[178,1595,1596,1599],{},[493,1597,1598],{},"Tiles are missing from the VRT."," They had a different CRS and were skipped; reproject them.",[178,1601,1602,1605],{},[493,1603,1604],{},"Black frames around tiles."," Source NoData was not declared.",[178,1607,1608,1611],{},[493,1609,1610],{},"Zoomed-out views are slow."," Build overviews.",[170,1613,1615],{"id":1614},"conclusion","Conclusion",[14,1617,1618],{},"Build mosaic VRTs to treat tiles as one raster and band-stack VRTs to combine separate band files, declare source NoData, keep sources in one CRS, make paths relative so the VRT travels, add overviews for fast display, and translate to a GeoTIFF or COG when a standalone file is required.",[170,1620,1622],{"id":1621},"frequently-asked-questions","Frequently Asked Questions",[14,1624,1625,1628,1629,1632],{},[493,1626,1627],{},"Can a VRT reproject?","\nA warped VRT can, written with ",[185,1630,1631],{},"gdalwarp -of VRT","; the mosaic VRT built here cannot.",[14,1634,1635,1638],{},[493,1636,1637],{},"Can I edit a VRT by hand?","\nYes; it is XML. Change paths, band descriptions or NoData with any editor, and keep a backup.",[14,1640,1641,1644],{},[493,1642,1643],{},"Are VRTs slower than GeoTIFFs?","\nSlightly, especially over many small files or network drives; overviews and local storage close most of the gap.",[14,1646,1647,1650],{},[493,1648,1649],{},"Can I store a VRT inside a QGIS project?","\nThe project stores the VRT's path like any raster source. Keep the VRT next to the project, or in a shared data folder with relative source paths, so the project opens on other machines without broken layers.",[14,1652,1653,1656],{},[493,1654,1655],{},"Can Processing algorithms use VRTs as input?","\nYes. Any algorithm that reads rasters through GDAL accepts a VRT.",[170,1658,1660],{"id":1659},"related","Related",[175,1662,1663,1668,1673,1678,1684],{},[178,1664,1665,1667],{},[21,1666,24],{"href":23}," — the guide this recipe belongs to",[178,1669,1670],{},[21,1671,1672],{"href":1562},"Merge Raster Tiles into a Mosaic in PyQGIS",[178,1674,1675],{},[21,1676,1677],{"href":1557},"Export a Cloud Optimized GeoTIFF in PyQGIS",[178,1679,1680],{},[21,1681,1683],{"href":1682},"\u002Fspatial-data-processing-automation\u002Fweb-services-and-remote-data\u002Fread-cloud-optimized-geotiff-pyqgis\u002F","Read a Cloud Optimized GeoTIFF in PyQGIS",[178,1685,1686],{},[21,1687,1689],{"href":1688},"\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fcalculate-raster-statistics-pyqgis\u002F","Calculate Raster Statistics in PyQGIS",[1691,1692,1693],"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 pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}",{"title":206,"searchDepth":229,"depth":229,"links":1695},[1696,1697,1698,1699,1700,1701,1702,1703,1704,1705,1706,1707],{"id":172,"depth":229,"text":173},{"id":191,"depth":229,"text":192},{"id":515,"depth":229,"text":516},{"id":800,"depth":229,"text":801},{"id":1007,"depth":229,"text":1008},{"id":1195,"depth":229,"text":1196},{"id":1471,"depth":229,"text":1472},{"id":1566,"depth":229,"text":1567},{"id":1584,"depth":229,"text":1585},{"id":1614,"depth":229,"text":1615},{"id":1621,"depth":229,"text":1622},{"id":1659,"depth":229,"text":1660},"Present many raster files as one layer without copying them — building a VRT mosaic with gdal:buildvirtualraster, stacking bands from separate files, choosing resolution and NoData, keeping paths portable, adding overviews and converting the VRT to a real file when needed.","md",{"slug":1711,"type":1712,"breadcrumb":1713,"datePublished":1714,"dateModified":1714},"build-virtual-raster-vrt-pyqgis","article","Build a Virtual Raster (VRT)","2026-10-02","\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fbuild-virtual-raster-vrt-pyqgis",{"title":5,"description":1708},"spatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fbuild-virtual-raster-vrt-pyqgis\u002Findex","urM4s0qF1whqjZlcoLaGz85ps5JgNWbqZxftlZkb_ek",1790966264481]