[{"data":1,"prerenderedAt":1514},["ShallowReactive",2],{"doc:\u002Fspatial-data-processing-automation\u002Fweb-services-and-remote-data\u002Fread-cloud-optimized-geotiff-pyqgis":3},{"id":4,"title":5,"body":6,"description":1503,"extension":1504,"meta":1505,"navigation":238,"path":1510,"seo":1511,"stem":1512,"__hash__":1513},"docs\u002Fspatial-data-processing-automation\u002Fweb-services-and-remote-data\u002Fread-cloud-optimized-geotiff-pyqgis\u002Findex.md","Read a Cloud-Optimized GeoTIFF in PyQGIS",{"type":7,"value":8,"toc":1490},"minimark",[9,13,17,26,179,184,201,205,360,382,385,483,504,508,511,594,606,706,710,717,831,847,963,967,970,1050,1086,1090,1093,1229,1245,1251,1255,1326,1333,1337,1387,1391,1404,1408,1414,1423,1429,1445,1451,1455,1486],[10,11,5],"h1",{"id":12},"read-a-cloud-optimized-geotiff-in-pyqgis",[14,15,16],"p",{},"A cloud-optimized GeoTIFF is an ordinary GeoTIFF with its bytes arranged so that a reader can fetch exactly the part it needs over HTTP. Internally tiled, with overviews, and with the header at the front — that is the whole specification. The consequence is disproportionate: a 40 GB national elevation model becomes a layer you add in a second and render from a few hundred kilobytes, without downloading anything or having anywhere to put it if you did.",[14,18,19,20,25],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fspatial-data-processing-automation\u002Fweb-services-and-remote-data\u002F","Web Services and Remote Data in PyQGIS",". It covers opening a COG from a URL or a bucket, the settings that decide whether it is fast or painful, verifying that a file really is cloud-optimized, and producing one.",[14,27,28],{},[29,30,35,39,43,50,67,76,86,92,102,107,111,115,120,124,128,132,137,141,146,150,153,157,160,164,168,172],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"xmlns":34},"0 0 760 284","img","Diagram contrasting a plain GeoTIFF that must be downloaded whole against a cloud optimized GeoTIFF where the reader fetches the header, then an overview level, then only the tiles covering the current view","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[36,37,38],"title",{},"Why the byte layout changes everything",[40,41,42],"desc",{},"A plain GeoTIFF stored on a web server must be transferred in full before it can be read, because its data is not tiled and its overviews may be absent or in a separate file. A cloud optimized GeoTIFF is read with three small range requests: the header, the appropriate overview level for the current zoom, and the individual tiles that intersect the view.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","284","#f6f3ea",[51,52,53],"defs",{},[54,55,62],"marker",{"id":56,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"cogArrow","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-weight:bold;font-family:sans-serif","#17211d","middle","Same 40 GB of pixels, two very different reads",[44,77],{"x":78,"y":79,"width":80,"height":81,"rx":82,"fill":83,"stroke":84,"style":85},"20","48","348","212","10","#fdf2e2","#b91c1c","stroke-width:2.5",[68,87,91],{"x":88,"y":89,"style":90,"fill":84,"textAnchor":74},"194","74","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","plain GeoTIFF over HTTP",[44,93],{"x":94,"y":95,"width":96,"height":97,"rx":98,"fill":99,"stroke":100,"style":101},"44","90","300","46","6","#fffdf7","#59645f","stroke-width:1.5",[68,103,106],{"x":88,"y":104,"style":105,"fill":66,"textAnchor":74},"112","text-anchor:middle;font-size:11px;font-family:sans-serif","stripped, not tiled",[68,108,110],{"x":88,"y":109,"style":105,"fill":66,"textAnchor":74},"130","overviews absent or in a sidecar",[44,112],{"x":94,"y":113,"width":96,"height":97,"rx":98,"fill":49,"stroke":84,"style":114},"146","stroke-width:2",[68,116,119],{"x":88,"y":117,"style":118,"fill":84,"textAnchor":74},"168","text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif","read the whole file to draw anything",[68,121,123],{"x":88,"y":122,"style":105,"fill":66,"textAnchor":74},"186","40 GB, every session",[44,125],{"x":94,"y":126,"width":96,"height":127,"rx":98,"fill":99,"stroke":100,"style":101},"202","42",[68,129,131],{"x":88,"y":130,"style":105,"fill":66,"textAnchor":74},"228","in practice: download it first, or give up",[44,133],{"x":134,"y":79,"width":80,"height":81,"rx":82,"fill":135,"stroke":136,"style":85},"392","#edf8e9","#15803d",[68,138,140],{"x":139,"y":89,"style":90,"fill":136,"textAnchor":74},"566","cloud-optimized GeoTIFF",[44,142],{"x":143,"y":95,"width":96,"height":144,"rx":98,"fill":99,"stroke":145,"style":101},"416","36","#0f766e",[68,147,149],{"x":139,"y":148,"style":105,"fill":66,"textAnchor":74},"113","request 1 — header, a few kilobytes",[44,151],{"x":143,"y":152,"width":96,"height":144,"rx":98,"fill":99,"stroke":145,"style":101},"132",[68,154,156],{"x":139,"y":155,"style":105,"fill":66,"textAnchor":74},"155","request 2 — the right overview level",[44,158],{"x":143,"y":159,"width":96,"height":144,"rx":98,"fill":99,"stroke":145,"style":101},"174",[68,161,163],{"x":139,"y":162,"style":105,"fill":66,"textAnchor":74},"197","request 3 — tiles under the view only",[44,165],{"x":143,"y":166,"width":96,"height":167,"rx":98,"fill":99,"stroke":136,"style":114},"216","30",[68,169,171],{"x":139,"y":170,"style":118,"fill":136,"textAnchor":74},"236","renders in about a second",[173,174],"line",{"x1":175,"y1":176,"x2":177,"y2":176,"stroke":66,"style":178},"368","150","386","stroke-width:2;marker-end:url(#cogArrow)",[180,181,183],"h2",{"id":182},"prerequisites","Prerequisites",[185,186,187,195,198],"ul",{},[188,189,190,194],"li",{},[191,192,193],"strong",{},"QGIS 3.34 LTR"," (bundled Python 3.12) or newer — GDAL 3.4 or above, which every 3.x LTS build ships.",[188,196,197],{},"A COG URL, or credentials for the bucket holding one.",[188,199,200],{},"Nothing installed: the virtual file system is part of GDAL and needs no extra packages.",[180,202,204],{"id":203},"open-a-cog-from-a-url","Open a COG from a URL",[206,207,212],"pre",{"className":208,"code":209,"language":210,"meta":211,"style":211},"language-python shiki shiki-themes github-dark","from qgis.core import QgsRasterLayer, QgsProject\n\nurl = \"\u002Fvsicurl\u002Fhttps:\u002F\u002Fstorage.example.org\u002Fdem\u002Fnational_dem_cog.tif\"\ndem = QgsRasterLayer(url, \"National DEM\", \"gdal\")\n\nif not dem.isValid():\n    raise RuntimeError(f\"could not open COG: {dem.error().summary()}\")\n\nprint(dem.width(), dem.height(), dem.bandCount())\nprint(dem.extent().toString(2))\nQgsProject.instance().addMapLayer(dem)\n","python","",[213,214,215,233,240,253,276,281,293,326,331,340,354],"code",{"__ignoreMap":211},[216,217,219,223,227,230],"span",{"class":173,"line":218},1,[216,220,222],{"class":221},"snl16","from",[216,224,226],{"class":225},"s95oV"," qgis.core ",[216,228,229],{"class":221},"import",[216,231,232],{"class":225}," QgsRasterLayer, QgsProject\n",[216,234,236],{"class":173,"line":235},2,[216,237,239],{"emptyLinePlaceholder":238},true,"\n",[216,241,243,246,249],{"class":173,"line":242},3,[216,244,245],{"class":225},"url ",[216,247,248],{"class":221},"=",[216,250,252],{"class":251},"sU2Wk"," \"\u002Fvsicurl\u002Fhttps:\u002F\u002Fstorage.example.org\u002Fdem\u002Fnational_dem_cog.tif\"\n",[216,254,256,259,261,264,267,270,273],{"class":173,"line":255},4,[216,257,258],{"class":225},"dem ",[216,260,248],{"class":221},[216,262,263],{"class":225}," QgsRasterLayer(url, ",[216,265,266],{"class":251},"\"National DEM\"",[216,268,269],{"class":225},", ",[216,271,272],{"class":251},"\"gdal\"",[216,274,275],{"class":225},")\n",[216,277,279],{"class":173,"line":278},5,[216,280,239],{"emptyLinePlaceholder":238},[216,282,284,287,290],{"class":173,"line":283},6,[216,285,286],{"class":221},"if",[216,288,289],{"class":221}," not",[216,291,292],{"class":225}," dem.isValid():\n",[216,294,296,299,303,306,309,312,315,318,321,324],{"class":173,"line":295},7,[216,297,298],{"class":221},"    raise",[216,300,302],{"class":301},"sDLfK"," RuntimeError",[216,304,305],{"class":225},"(",[216,307,308],{"class":221},"f",[216,310,311],{"class":251},"\"could not open COG: ",[216,313,314],{"class":301},"{",[216,316,317],{"class":225},"dem.error().summary()",[216,319,320],{"class":301},"}",[216,322,323],{"class":251},"\"",[216,325,275],{"class":225},[216,327,329],{"class":173,"line":328},8,[216,330,239],{"emptyLinePlaceholder":238},[216,332,334,337],{"class":173,"line":333},9,[216,335,336],{"class":301},"print",[216,338,339],{"class":225},"(dem.width(), dem.height(), dem.bandCount())\n",[216,341,343,345,348,351],{"class":173,"line":342},10,[216,344,336],{"class":301},[216,346,347],{"class":225},"(dem.extent().toString(",[216,349,350],{"class":301},"2",[216,352,353],{"class":225},"))\n",[216,355,357],{"class":173,"line":356},11,[216,358,359],{"class":225},"QgsProject.instance().addMapLayer(dem)\n",[14,361,362,365,366,369,370,373,374,377,378,381],{},[191,363,364],{},"Breakdown:"," The ",[213,367,368],{},"\u002Fvsicurl\u002F"," prefix tells GDAL to read the file over HTTP with range requests rather than downloading it, and everything after the prefix is an ordinary URL. The layer is a normal ",[213,371,372],{},"gdal"," raster: it has an extent, a band count, statistics, and every raster algorithm accepts it. The dimensions printed here come from the header alone, typically one request of a few kilobytes against a file of many gigabytes — which is the whole trick. If ",[213,375,376],{},"isValid()"," is ",[213,379,380],{},"False",", the usual causes are a URL that redirects (GDAL follows redirects but not all of them), a server that does not support range requests, or a file that is not really a GeoTIFF.",[14,383,384],{},"For object storage, swap the prefix and let GDAL pick up credentials from the environment:",[206,386,388],{"className":208,"code":387,"language":210,"meta":211,"style":211},"import os\n\nos.environ[\"AWS_S3_ENDPOINT\"] = \"s3.eu-west-2.amazonaws.com\"\nos.environ[\"AWS_ACCESS_KEY_ID\"] = os.environ[\"MY_KEY_ID\"]\nos.environ[\"AWS_SECRET_ACCESS_KEY\"] = os.environ[\"MY_SECRET\"]\n\nlayer = QgsRasterLayer(\"\u002Fvsis3\u002Fmy-bucket\u002Fimagery\u002F2026-06-mosaic.tif\", \"Mosaic\", \"gdal\")\n",[213,389,390,397,401,417,437,455,459],{"__ignoreMap":211},[216,391,392,394],{"class":173,"line":218},[216,393,229],{"class":221},[216,395,396],{"class":225}," os\n",[216,398,399],{"class":173,"line":235},[216,400,239],{"emptyLinePlaceholder":238},[216,402,403,406,409,412,414],{"class":173,"line":242},[216,404,405],{"class":225},"os.environ[",[216,407,408],{"class":251},"\"AWS_S3_ENDPOINT\"",[216,410,411],{"class":225},"] ",[216,413,248],{"class":221},[216,415,416],{"class":251}," \"s3.eu-west-2.amazonaws.com\"\n",[216,418,419,421,424,426,428,431,434],{"class":173,"line":255},[216,420,405],{"class":225},[216,422,423],{"class":251},"\"AWS_ACCESS_KEY_ID\"",[216,425,411],{"class":225},[216,427,248],{"class":221},[216,429,430],{"class":225}," os.environ[",[216,432,433],{"class":251},"\"MY_KEY_ID\"",[216,435,436],{"class":225},"]\n",[216,438,439,441,444,446,448,450,453],{"class":173,"line":278},[216,440,405],{"class":225},[216,442,443],{"class":251},"\"AWS_SECRET_ACCESS_KEY\"",[216,445,411],{"class":225},[216,447,248],{"class":221},[216,449,430],{"class":225},[216,451,452],{"class":251},"\"MY_SECRET\"",[216,454,436],{"class":225},[216,456,457],{"class":173,"line":283},[216,458,239],{"emptyLinePlaceholder":238},[216,460,461,464,466,469,472,474,477,479,481],{"class":173,"line":295},[216,462,463],{"class":225},"layer ",[216,465,248],{"class":221},[216,467,468],{"class":225}," QgsRasterLayer(",[216,470,471],{"class":251},"\"\u002Fvsis3\u002Fmy-bucket\u002Fimagery\u002F2026-06-mosaic.tif\"",[216,473,269],{"class":225},[216,475,476],{"class":251},"\"Mosaic\"",[216,478,269],{"class":225},[216,480,272],{"class":251},[216,482,275],{"class":225},[14,484,485,487,488,491,492,495,496,499,500,503],{},[191,486,364],{}," ",[213,489,490],{},"\u002Fvsis3\u002F"," reads from S3-compatible storage, ",[213,493,494],{},"\u002Fvsiaz\u002F"," from Azure Blob Storage, ",[213,497,498],{},"\u002Fvsigs\u002F"," from Google Cloud Storage, each with its own environment variables. Reading the secrets from the environment rather than writing them in the script is the point — the same discipline as the authentication database for services, applied where GDAL rather than QGIS is doing the fetching. For public buckets, ",[213,501,502],{},"AWS_NO_SIGN_REQUEST=YES"," avoids the need for credentials entirely.",[180,505,507],{"id":506},"tune-the-request-behaviour","Tune the request behaviour",[14,509,510],{},"The defaults are conservative. Three settings decide whether a COG feels local or sluggish.",[206,512,514],{"className":208,"code":513,"language":210,"meta":211,"style":211},"from osgeo import gdal\n\ngdal.SetConfigOption(\"GDAL_DISABLE_READDIR_ON_OPEN\", \"EMPTY_DIR\")\ngdal.SetConfigOption(\"CPL_VSIL_CURL_ALLOWED_EXTENSIONS\", \".tif,.tiff,.ovr\")\ngdal.SetConfigOption(\"VSI_CACHE\", \"TRUE\")\ngdal.SetConfigOption(\"VSI_CACHE_SIZE\", \"50000000\")     # 50 MB per file\n",[213,515,516,528,532,547,561,575],{"__ignoreMap":211},[216,517,518,520,523,525],{"class":173,"line":218},[216,519,222],{"class":221},[216,521,522],{"class":225}," osgeo ",[216,524,229],{"class":221},[216,526,527],{"class":225}," gdal\n",[216,529,530],{"class":173,"line":235},[216,531,239],{"emptyLinePlaceholder":238},[216,533,534,537,540,542,545],{"class":173,"line":242},[216,535,536],{"class":225},"gdal.SetConfigOption(",[216,538,539],{"class":251},"\"GDAL_DISABLE_READDIR_ON_OPEN\"",[216,541,269],{"class":225},[216,543,544],{"class":251},"\"EMPTY_DIR\"",[216,546,275],{"class":225},[216,548,549,551,554,556,559],{"class":173,"line":255},[216,550,536],{"class":225},[216,552,553],{"class":251},"\"CPL_VSIL_CURL_ALLOWED_EXTENSIONS\"",[216,555,269],{"class":225},[216,557,558],{"class":251},"\".tif,.tiff,.ovr\"",[216,560,275],{"class":225},[216,562,563,565,568,570,573],{"class":173,"line":278},[216,564,536],{"class":225},[216,566,567],{"class":251},"\"VSI_CACHE\"",[216,569,269],{"class":225},[216,571,572],{"class":251},"\"TRUE\"",[216,574,275],{"class":225},[216,576,577,579,582,584,587,590],{"class":173,"line":283},[216,578,536],{"class":225},[216,580,581],{"class":251},"\"VSI_CACHE_SIZE\"",[216,583,269],{"class":225},[216,585,586],{"class":251},"\"50000000\"",[216,588,589],{"class":225},")     ",[216,591,593],{"class":592},"sjoCn","# 50 MB per file\n",[14,595,596,487,598,601,602,605],{},[191,597,364],{},[213,599,600],{},"GDAL_DISABLE_READDIR_ON_OPEN"," set to ",[213,603,604],{},"EMPTY_DIR"," stops GDAL listing the whole remote directory when opening one file, which on a bucket of ten thousand scenes is the difference between opening in a second and opening in a minute. Restricting the allowed extensions prevents speculative requests for sidecar files that do not exist — each one a wasted round trip. The VSI cache keeps recently read blocks in memory, so panning back to where you were does not re-fetch. Set these once at the start of a script, before opening any remote raster; they are process-wide.",[14,607,608],{},[29,609,612,615,618,621,624,632,636,642,646,650,654,658,661,665,670,674,677,680,683,685,689,693,697,701],{"viewBox":610,"role":32,"ariaLabel":611,"xmlns":34},"0 0 760 240","Sequence showing requests made when opening a remote raster with default settings compared with tuned settings, where directory listing and sidecar probing are eliminated",[36,613,614],{},"Where the wasted round trips go",[40,616,617],{},"With default settings, opening a remote raster lists the containing directory, probes for several sidecar files that do not exist, and only then reads the header. With the recommended configuration options, the directory listing and the sidecar probes are skipped and the first request is the header itself.",[44,619],{"x":46,"y":46,"width":47,"height":620,"fill":49},"240",[68,622,623],{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"Most of the wait is requests for files that are not there",[68,625,631],{"x":626,"y":627,"style":628,"fill":629,"textAnchor":630},"140","76","text-anchor:end;font-size:11px;font-weight:bold;font-family:sans-serif","#b45309","end","defaults",[44,633],{"x":634,"y":635,"width":176,"height":167,"rx":59,"fill":83,"stroke":629,"style":101},"152","58",[68,637,641],{"x":638,"y":639,"style":640,"fill":66,"textAnchor":74},"227","78","text-anchor:middle;font-size:10px;font-family:sans-serif","list the directory",[44,643],{"x":644,"y":635,"width":645,"height":167,"rx":59,"fill":83,"stroke":629,"style":101},"310","118",[68,647,649],{"x":648,"y":639,"style":640,"fill":66,"textAnchor":74},"369","probe .aux.xml",[44,651],{"x":652,"y":635,"width":653,"height":167,"rx":59,"fill":83,"stroke":629,"style":101},"436","104",[68,655,657],{"x":656,"y":639,"style":640,"fill":66,"textAnchor":74},"488","probe .ovr",[44,659],{"x":660,"y":635,"width":653,"height":167,"rx":59,"fill":83,"stroke":629,"style":101},"548",[68,662,664],{"x":663,"y":639,"style":640,"fill":66,"textAnchor":74},"600","probe .msk",[44,666],{"x":667,"y":635,"width":668,"height":167,"rx":59,"fill":669,"stroke":145,"style":114},"660","84","#eef7f4",[68,671,673],{"x":672,"y":639,"style":640,"fill":66,"textAnchor":74},"702","header",[68,675,676],{"x":626,"y":113,"style":628,"fill":136,"textAnchor":630},"tuned",[44,678],{"x":634,"y":679,"width":176,"height":167,"rx":59,"fill":669,"stroke":145,"style":114},"128",[68,681,673],{"x":638,"y":682,"style":640,"fill":66,"textAnchor":74},"148",[44,684],{"x":644,"y":679,"width":176,"height":167,"rx":59,"fill":135,"stroke":136,"style":114},[68,686,688],{"x":687,"y":682,"style":640,"fill":66,"textAnchor":74},"385","overview level",[44,690],{"x":691,"y":679,"width":692,"height":167,"rx":59,"fill":135,"stroke":136,"style":114},"468","184",[68,694,696],{"x":695,"y":682,"style":640,"fill":66,"textAnchor":74},"560","tiles under the view",[44,698],{"x":634,"y":122,"width":699,"height":700,"rx":58,"fill":99,"stroke":100,"style":101},"592","38",[68,702,705],{"x":703,"y":704,"style":105,"fill":66,"textAnchor":74},"448","210","Four fewer round trips per file matters most when you open a thousand of them",[180,707,709],{"id":708},"check-whether-a-file-really-is-cloud-optimized","Check whether a file really is cloud-optimized",[14,711,712,713,716],{},"A file named ",[213,714,715],{},"_cog.tif"," is not necessarily one. GDAL ships a validator, and the answer changes how you should use the file.",[206,718,720],{"className":208,"code":719,"language":210,"meta":211,"style":211},"from osgeo import gdal\n\ninfo = gdal.Info(\"\u002Fvsicurl\u002Fhttps:\u002F\u002Fstorage.example.org\u002Fdem\u002Fnational_dem_cog.tif\",\n                 format=\"json\")\n\nprint(info[\"metadata\"].get(\"IMAGE_STRUCTURE\", {}))\nprint([band.get(\"overviews\", []) for band in info[\"bands\"]])\nprint(info.get(\"blockSize\"))\n",[213,721,722,732,736,752,765,769,788,819],{"__ignoreMap":211},[216,723,724,726,728,730],{"class":173,"line":218},[216,725,222],{"class":221},[216,727,522],{"class":225},[216,729,229],{"class":221},[216,731,527],{"class":225},[216,733,734],{"class":173,"line":235},[216,735,239],{"emptyLinePlaceholder":238},[216,737,738,741,743,746,749],{"class":173,"line":242},[216,739,740],{"class":225},"info ",[216,742,248],{"class":221},[216,744,745],{"class":225}," gdal.Info(",[216,747,748],{"class":251},"\"\u002Fvsicurl\u002Fhttps:\u002F\u002Fstorage.example.org\u002Fdem\u002Fnational_dem_cog.tif\"",[216,750,751],{"class":225},",\n",[216,753,754,758,760,763],{"class":173,"line":255},[216,755,757],{"class":756},"s9osk","                 format",[216,759,248],{"class":221},[216,761,762],{"class":251},"\"json\"",[216,764,275],{"class":225},[216,766,767],{"class":173,"line":278},[216,768,239],{"emptyLinePlaceholder":238},[216,770,771,773,776,779,782,785],{"class":173,"line":283},[216,772,336],{"class":301},[216,774,775],{"class":225},"(info[",[216,777,778],{"class":251},"\"metadata\"",[216,780,781],{"class":225},"].get(",[216,783,784],{"class":251},"\"IMAGE_STRUCTURE\"",[216,786,787],{"class":225},", {}))\n",[216,789,790,792,795,798,801,804,807,810,813,816],{"class":173,"line":295},[216,791,336],{"class":301},[216,793,794],{"class":225},"([band.get(",[216,796,797],{"class":251},"\"overviews\"",[216,799,800],{"class":225},", []) ",[216,802,803],{"class":221},"for",[216,805,806],{"class":225}," band ",[216,808,809],{"class":221},"in",[216,811,812],{"class":225}," info[",[216,814,815],{"class":251},"\"bands\"",[216,817,818],{"class":225},"]])\n",[216,820,821,823,826,829],{"class":173,"line":328},[216,822,336],{"class":301},[216,824,825],{"class":225},"(info.get(",[216,827,828],{"class":251},"\"blockSize\"",[216,830,353],{"class":225},[14,832,833,835,836,839,840,842,843,846],{},[191,834,364],{}," Three properties decide it: the file must be internally tiled rather than stripped, which shows up as a block size like 512 by 512 instead of a full-width strip; it must carry overviews inside the file rather than in a ",[213,837,838],{},".ovr"," sidecar; and the header must sit at the front. A file failing the first two is still readable over ",[213,841,368],{},", but each render will pull far more bytes than it should — often the whole file — which is exactly the situation the format exists to prevent. GDAL's ",[213,844,845],{},"validate_cloud_optimized_geotiff.py"," script gives a definitive verdict if you need one for a data-publishing checklist.",[14,848,849],{},[29,850,853,856,859,862,865,871,874,879,884,887,891,894,898,901,905,910,913,917,924,930,934,937,940,943,947,952,954,957,960],{"viewBox":851,"role":32,"ariaLabel":852,"xmlns":34},"0 0 760 246","Diagram of the three structural properties that make a GeoTIFF cloud optimized, internal tiling, internal overviews and a header at the front",[36,854,855],{},"Three properties, all three required",[40,857,858],{},"Internal tiling means pixels are stored in square blocks, so a view can fetch only the blocks it overlaps. Internal overviews mean coarse versions live in the same file, so a zoomed-out view reads a small fraction of the data. A header at the front means the reader learns the layout in one request. A file missing any of the three is readable but not efficient.",[44,860],{"x":46,"y":46,"width":47,"height":861,"fill":49},"246",[68,863,864],{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"Miss one and it is a GeoTIFF on a web server, nothing more",[44,866],{"x":867,"y":868,"width":869,"height":870,"rx":82,"fill":669,"stroke":145,"style":85},"16","52","232","176",[68,872,873],{"x":152,"y":639,"style":118,"fill":145,"textAnchor":74},"internal tiling",[44,875],{"x":876,"y":877,"width":152,"height":876,"rx":878,"fill":99,"stroke":100,"style":101},"66","94","4",[173,880],{"x1":881,"y1":877,"x2":881,"y2":882,"stroke":100,"style":883},"110","160","stroke-width:1",[173,885],{"x1":886,"y1":877,"x2":886,"y2":882,"stroke":100,"style":883},"154",[173,888],{"x1":876,"y1":889,"x2":890,"y2":889,"stroke":100,"style":883},"116","198",[173,892],{"x1":876,"y1":893,"x2":890,"y2":893,"stroke":100,"style":883},"138",[44,895],{"x":881,"y":889,"width":94,"height":896,"fill":145,"fillOpacity":897,"stroke":145,"style":114},"22",0.35,[68,899,900],{"x":152,"y":122,"style":105,"fill":66,"textAnchor":74},"fetch only the blocks",[68,902,904],{"x":152,"y":903,"style":105,"fill":66,"textAnchor":74},"208","the view overlaps",[44,906],{"x":907,"y":868,"width":869,"height":870,"rx":82,"fill":908,"stroke":909,"style":85},"264","#eff3ff","#2563eb",[68,911,912],{"x":70,"y":639,"style":118,"fill":909,"textAnchor":74},"internal overviews",[44,914],{"x":96,"y":877,"width":915,"height":868,"rx":878,"fill":909,"fillOpacity":916,"stroke":909,"style":114},"72",0.3,[44,918],{"x":919,"y":920,"width":94,"height":921,"rx":922,"fill":909,"fillOpacity":923,"stroke":909,"style":101},"384","100","32","3",0.25,[44,925],{"x":926,"y":927,"width":928,"height":78,"rx":922,"fill":909,"fillOpacity":929,"stroke":909,"style":101},"438","106","26",0.2,[68,931,933],{"x":70,"y":932,"style":105,"fill":66,"textAnchor":74},"172","coarse copies in the",[68,935,936],{"x":70,"y":88,"style":105,"fill":66,"textAnchor":74},"same file, not a sidecar",[68,938,939],{"x":70,"y":166,"style":640,"fill":100,"textAnchor":74},"zooming out stays cheap",[44,941],{"x":942,"y":868,"width":869,"height":870,"rx":82,"fill":83,"stroke":629,"style":85},"512",[68,944,946],{"x":945,"y":639,"style":118,"fill":629,"textAnchor":74},"628","header at the front",[44,948],{"x":949,"y":920,"width":950,"height":951,"rx":878,"fill":99,"stroke":100,"style":101},"546","164","34",[44,953],{"x":949,"y":920,"width":951,"height":951,"rx":878,"fill":629,"fillOpacity":897,"stroke":629,"style":114},[68,955,956],{"x":945,"y":950,"style":105,"fill":66,"textAnchor":74},"one small request",[68,958,959],{"x":945,"y":122,"style":105,"fill":66,"textAnchor":74},"learns the whole layout",[68,961,962],{"x":945,"y":903,"style":640,"fill":100,"textAnchor":74},"no seeking to the end",[180,964,966],{"id":965},"write-a-cog-of-your-own","Write a COG of your own",[14,968,969],{},"Anything you produce for others to read over the network should be one, and it is a single algorithm call.",[206,971,973],{"className":208,"code":972,"language":210,"meta":211,"style":211},"import processing\n\nprocessing.run(\"gdal:translate\", {\n    \"INPUT\": \"\u002Fdata\u002Foutputs\u002Fslope.tif\",\n    \"OPTIONS\": \"COMPRESS=DEFLATE|PREDICTOR=2|TILED=YES|COPY_SRC_OVERVIEWS=YES\",\n    \"DATA_TYPE\": 0,\n    \"OUTPUT\": \"\u002Fdata\u002Fpublish\u002Fslope_cog.tif\",\n})\n",[213,974,975,982,986,997,1010,1022,1033,1045],{"__ignoreMap":211},[216,976,977,979],{"class":173,"line":218},[216,978,229],{"class":221},[216,980,981],{"class":225}," processing\n",[216,983,984],{"class":173,"line":235},[216,985,239],{"emptyLinePlaceholder":238},[216,987,988,991,994],{"class":173,"line":242},[216,989,990],{"class":225},"processing.run(",[216,992,993],{"class":251},"\"gdal:translate\"",[216,995,996],{"class":225},", {\n",[216,998,999,1002,1005,1008],{"class":173,"line":255},[216,1000,1001],{"class":251},"    \"INPUT\"",[216,1003,1004],{"class":225},": ",[216,1006,1007],{"class":251},"\"\u002Fdata\u002Foutputs\u002Fslope.tif\"",[216,1009,751],{"class":225},[216,1011,1012,1015,1017,1020],{"class":173,"line":278},[216,1013,1014],{"class":251},"    \"OPTIONS\"",[216,1016,1004],{"class":225},[216,1018,1019],{"class":251},"\"COMPRESS=DEFLATE|PREDICTOR=2|TILED=YES|COPY_SRC_OVERVIEWS=YES\"",[216,1021,751],{"class":225},[216,1023,1024,1027,1029,1031],{"class":173,"line":283},[216,1025,1026],{"class":251},"    \"DATA_TYPE\"",[216,1028,1004],{"class":225},[216,1030,46],{"class":301},[216,1032,751],{"class":225},[216,1034,1035,1038,1040,1043],{"class":173,"line":295},[216,1036,1037],{"class":251},"    \"OUTPUT\"",[216,1039,1004],{"class":225},[216,1041,1042],{"class":251},"\"\u002Fdata\u002Fpublish\u002Fslope_cog.tif\"",[216,1044,751],{"class":225},[216,1046,1047],{"class":173,"line":328},[216,1048,1049],{"class":225},"})\n",[14,1051,1052,487,1054,1057,1058,1061,1062,1065,1066,1069,1070,1073,1074,1076,1077,1080,1081,1085],{},[191,1053,364],{},[213,1055,1056],{},"TILED=YES"," gives the internal tiling; ",[213,1059,1060],{},"COPY_SRC_OVERVIEWS=YES"," moves existing overviews into the file, so build them first with ",[213,1063,1064],{},"gdal:overviews"," if the source has none. ",[213,1067,1068],{},"DEFLATE"," with a horizontal predictor compresses continuous data such as elevation well; for categorical rasters drop the predictor. ",[213,1071,1072],{},"DATA_TYPE"," of ",[213,1075,46],{}," means \"use the input type\", which avoids the silent precision loss of an unintended conversion. Newer GDAL versions accept ",[213,1078,1079],{},"COG"," as an output driver directly, which handles the layout rules for you and is worth preferring where available. The Processing patterns around this call are covered in ",[21,1082,1084],{"href":1083},"\u002Fspatial-data-processing-automation\u002Fbatch-processing-with-pyqgis\u002Frun-processing-algorithm-from-script\u002F","Run a Processing Algorithm from a Script",".",[180,1087,1089],{"id":1088},"mosaic-many-remote-rasters-into-one-layer","Mosaic many remote rasters into one layer",[14,1091,1092],{},"Archives publish scenes, not countries: a thousand COGs tiling an area, each a separate file. A GDAL virtual raster stitches them into a single layer without copying a byte.",[206,1094,1096],{"className":208,"code":1095,"language":210,"meta":211,"style":211},"from osgeo import gdal\n\nscenes = [\n    \"\u002Fvsicurl\u002Fhttps:\u002F\u002Fstorage.example.org\u002Fdem\u002Ftile_0001.tif\",\n    \"\u002Fvsicurl\u002Fhttps:\u002F\u002Fstorage.example.org\u002Fdem\u002Ftile_0002.tif\",\n    \"\u002Fvsicurl\u002Fhttps:\u002F\u002Fstorage.example.org\u002Fdem\u002Ftile_0003.tif\",\n]\n\ngdal.BuildVRT(\"\u002Fdata\u002Fcache\u002Fnational_dem.vrt\", scenes,\n              options=gdal.BuildVRTOptions(resolution=\"highest\", addAlpha=False))\n\nmosaic = QgsRasterLayer(\"\u002Fdata\u002Fcache\u002Fnational_dem.vrt\", \"National DEM\", \"gdal\")\nprint(mosaic.isValid(), mosaic.extent().toString(2))\n",[213,1097,1098,1108,1112,1122,1129,1136,1143,1147,1151,1162,1191,1195,1217],{"__ignoreMap":211},[216,1099,1100,1102,1104,1106],{"class":173,"line":218},[216,1101,222],{"class":221},[216,1103,522],{"class":225},[216,1105,229],{"class":221},[216,1107,527],{"class":225},[216,1109,1110],{"class":173,"line":235},[216,1111,239],{"emptyLinePlaceholder":238},[216,1113,1114,1117,1119],{"class":173,"line":242},[216,1115,1116],{"class":225},"scenes ",[216,1118,248],{"class":221},[216,1120,1121],{"class":225}," [\n",[216,1123,1124,1127],{"class":173,"line":255},[216,1125,1126],{"class":251},"    \"\u002Fvsicurl\u002Fhttps:\u002F\u002Fstorage.example.org\u002Fdem\u002Ftile_0001.tif\"",[216,1128,751],{"class":225},[216,1130,1131,1134],{"class":173,"line":278},[216,1132,1133],{"class":251},"    \"\u002Fvsicurl\u002Fhttps:\u002F\u002Fstorage.example.org\u002Fdem\u002Ftile_0002.tif\"",[216,1135,751],{"class":225},[216,1137,1138,1141],{"class":173,"line":283},[216,1139,1140],{"class":251},"    \"\u002Fvsicurl\u002Fhttps:\u002F\u002Fstorage.example.org\u002Fdem\u002Ftile_0003.tif\"",[216,1142,751],{"class":225},[216,1144,1145],{"class":173,"line":295},[216,1146,436],{"class":225},[216,1148,1149],{"class":173,"line":328},[216,1150,239],{"emptyLinePlaceholder":238},[216,1152,1153,1156,1159],{"class":173,"line":333},[216,1154,1155],{"class":225},"gdal.BuildVRT(",[216,1157,1158],{"class":251},"\"\u002Fdata\u002Fcache\u002Fnational_dem.vrt\"",[216,1160,1161],{"class":225},", scenes,\n",[216,1163,1164,1167,1169,1172,1175,1177,1180,1182,1185,1187,1189],{"class":173,"line":342},[216,1165,1166],{"class":756},"              options",[216,1168,248],{"class":221},[216,1170,1171],{"class":225},"gdal.BuildVRTOptions(",[216,1173,1174],{"class":756},"resolution",[216,1176,248],{"class":221},[216,1178,1179],{"class":251},"\"highest\"",[216,1181,269],{"class":225},[216,1183,1184],{"class":756},"addAlpha",[216,1186,248],{"class":221},[216,1188,380],{"class":301},[216,1190,353],{"class":225},[216,1192,1193],{"class":173,"line":356},[216,1194,239],{"emptyLinePlaceholder":238},[216,1196,1198,1201,1203,1205,1207,1209,1211,1213,1215],{"class":173,"line":1197},12,[216,1199,1200],{"class":225},"mosaic ",[216,1202,248],{"class":221},[216,1204,468],{"class":225},[216,1206,1158],{"class":251},[216,1208,269],{"class":225},[216,1210,266],{"class":251},[216,1212,269],{"class":225},[216,1214,272],{"class":251},[216,1216,275],{"class":225},[216,1218,1220,1222,1225,1227],{"class":173,"line":1219},13,[216,1221,336],{"class":301},[216,1223,1224],{"class":225},"(mosaic.isValid(), mosaic.extent().toString(",[216,1226,350],{"class":301},[216,1228,353],{"class":225},[14,1230,1231,487,1233,1236,1237,1240,1241,1244],{},[191,1232,364],{},[213,1234,1235],{},"BuildVRT"," writes a small XML file listing the sources and their positions; the pixels stay where they are, so the local artefact is a few hundred kilobytes describing terabytes. Reading it opens only the sources that intersect the current view, which combines with the range-request behaviour to give a national mosaic that renders like a local file. ",[213,1238,1239],{},"resolution=\"highest\""," keeps the finest pixel size among the inputs, which is the safe default when scenes differ; mixing resolutions without saying so produces a mosaic quietly resampled to the coarsest. Sources must share a coordinate system — build a warped VRT with ",[213,1242,1243],{},"gdal.Warp"," first if they do not.",[14,1246,1247,1248,1250],{},"The VRT is also the right place to attach a scene list produced from a catalogue query, which is how most cloud archives are meant to be used: search the catalogue for what intersects your area of interest, feed the resulting URLs into ",[213,1249,1235],{},", and treat the result as an ordinary raster layer for the rest of the analysis.",[180,1252,1254],{"id":1253},"qgis-version-compatibility","QGIS version compatibility",[1256,1257,1258,1274],"table",{},[1259,1260,1261],"thead",{},[1262,1263,1264,1268,1271],"tr",{},[1265,1266,1267],"th",{},"QGIS version",[1265,1269,1270],{},"Python",[1265,1272,1273],{},"Notes",[1275,1276,1277,1295,1305,1316],"tbody",{},[1262,1278,1279,1283,1286],{},[1280,1281,1282],"td",{},"3.22 LTR",[1280,1284,1285],{},"3.9",[1280,1287,1288,1289,1291,1292,1294],{},"GDAL 3.2+; ",[213,1290,368],{}," and ",[213,1293,490],{}," available, COG driver present in most builds.",[1262,1296,1297,1300,1302],{},[1280,1298,1299],{},"3.28 LTR",[1280,1301,1285],{},[1280,1303,1304],{},"Identical.",[1262,1306,1307,1310,1313],{},[1280,1308,1309],{},"3.34 LTR",[1280,1311,1312],{},"3.12",[1280,1314,1315],{},"Baseline for this page; GDAL 3.8 with improved range-request batching.",[1262,1317,1318,1321,1323],{},[1280,1319,1320],{},"3.40 \u002F 3.44",[1280,1322,1312],{},[1280,1324,1325],{},"Identical usage; newer GDAL adds better multi-threaded reads for remote rasters.",[14,1327,1328,1329,1332],{},"The virtual file system belongs to GDAL rather than QGIS, so behaviour tracks the GDAL version your QGIS was built against — check it with ",[213,1330,1331],{},"gdal.VersionInfo()"," when a documented option appears to be ignored.",[180,1334,1336],{"id":1335},"troubleshooting","Troubleshooting",[185,1338,1339,1349,1360,1366,1372,1381],{},[188,1340,1341,1344,1345,1348],{},[191,1342,1343],{},"The layer is invalid over HTTPS but the URL works in a browser."," The server does not support range requests, or a redirect is losing the range header. Test with ",[213,1346,1347],{},"gdal.Info()"," for the real error.",[188,1350,1351,1354,1355,1357,1358,1085],{},[191,1352,1353],{},"Opening is slow but rendering is fast."," GDAL is listing the directory or probing sidecars. Set ",[213,1356,600],{}," to ",[213,1359,604],{},[188,1361,1362,1365],{},[191,1363,1364],{},"Zooming out is slower than zooming in."," The file has no internal overviews, so a whole-country view reads full-resolution pixels. It is not a real COG; rebuild it.",[188,1367,1368,1371],{},[191,1369,1370],{},"Credentials are ignored."," The environment variables must be set before the layer is opened, in the same process. Setting them in a shell that launched QGIS earlier does not help a script running later in a different session.",[188,1373,1374,1377,1378,1380],{},[191,1375,1376],{},"Statistics take forever."," Computing them reads the whole raster. Use ",[213,1379,1347],{}," with approximate statistics, or accept the layer's default rendering.",[188,1382,1383,1386],{},[191,1384,1385],{},"Processing writes a plain TIFF."," Most algorithms do. Convert the output explicitly with the options above before publishing it.",[180,1388,1390],{"id":1389},"conclusion","Conclusion",[14,1392,1393,1394,1396,1397,269,1399,269,1401,1403],{},"Prefix the URL with ",[213,1395,368],{}," — or ",[213,1398,490],{},[213,1400,494],{},[213,1402,498],{}," — and a cloud-hosted raster becomes an ordinary GDAL layer. Set the three configuration options that stop GDAL wasting round trips, verify that the file is genuinely tiled with internal overviews before relying on it, and write your own outputs as COGs so the next person can do the same.",[180,1405,1407],{"id":1406},"frequently-asked-questions","Frequently Asked Questions",[14,1409,1410,1413],{},[191,1411,1412],{},"Do I need to download the file first?","\nNo — that is the point. GDAL fetches only the byte ranges it needs, so a multi-gigabyte raster renders from a few hundred kilobytes.",[14,1415,1416,1419,1420,1422],{},[191,1417,1418],{},"Does this work for anything other than GeoTIFF?","\nYes. The virtual file system is format-agnostic, so ",[213,1421,368],{}," works with any GDAL-readable format; only GeoTIFF has the cloud-optimized layout that makes partial reads efficient.",[14,1424,1425,1428],{},[191,1426,1427],{},"Can Processing algorithms run against a remote raster?","\nThey can, and they will read whatever they need over the network. For anything that touches every pixel, copy it locally first — the transfer happens either way, and locally it happens once.",[14,1430,1431,1434,1435,1438,1439,1291,1442,1085],{},[191,1432,1433],{},"How do I read a COG inside a zip on a server?","\nChain the prefixes: ",[213,1436,1437],{},"\u002Fvsizip\u002F\u002Fvsicurl\u002Fhttps:\u002F\u002F...\u002Farchive.zip\u002Fdem.tif",". GDAL composes virtual file systems, which also covers ",[213,1440,1441],{},"\u002Fvsigzip\u002F",[213,1443,1444],{},"\u002Fvsitar\u002F",[14,1446,1447,1450],{},[191,1448,1449],{},"Is a COG worse than a plain GeoTIFF locally?","\nNo. It is a valid GeoTIFF that any reader opens normally; internal tiling and overviews usually make local rendering faster too.",[180,1452,1454],{"id":1453},"related","Related",[185,1456,1457,1462,1468,1474,1480],{},[188,1458,1459,1461],{},[21,1460,24],{"href":23}," — the guide this recipe belongs to",[188,1463,1464],{},[21,1465,1467],{"href":1466},"\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002F","Raster Analysis Workflows in PyQGIS",[188,1469,1470],{},[21,1471,1473],{"href":1472},"\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fcalculate-raster-statistics-pyqgis\u002F","Calculate Raster Statistics in PyQGIS",[188,1475,1476],{},[21,1477,1479],{"href":1478},"\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fclip-raster-by-mask-layer-pyqgis\u002F","Clip a Raster by Mask Layer in PyQGIS",[188,1481,1482],{},[21,1483,1485],{"href":1484},"\u002Fspatial-data-processing-automation\u002Fweb-services-and-remote-data\u002Fload-wms-layer-pyqgis\u002F","Load a WMS Layer in PyQGIS",[1487,1488,1489],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":211,"searchDepth":235,"depth":235,"links":1491},[1492,1493,1494,1495,1496,1497,1498,1499,1500,1501,1502],{"id":182,"depth":235,"text":183},{"id":203,"depth":235,"text":204},{"id":506,"depth":235,"text":507},{"id":708,"depth":235,"text":709},{"id":965,"depth":235,"text":966},{"id":1088,"depth":235,"text":1089},{"id":1253,"depth":235,"text":1254},{"id":1335,"depth":235,"text":1336},{"id":1389,"depth":235,"text":1390},{"id":1406,"depth":235,"text":1407},{"id":1453,"depth":235,"text":1454},"Open a COG straight from HTTP or object storage with GDAL's virtual file system, tune the range-request settings, check that a file really is cloud-optimized, and write one of your own.","md",{"slug":1506,"type":1507,"breadcrumb":1508,"datePublished":1509,"dateModified":1509},"read-cloud-optimized-geotiff-pyqgis","article","Cloud-Optimized GeoTIFF","2026-08-15","\u002Fspatial-data-processing-automation\u002Fweb-services-and-remote-data\u002Fread-cloud-optimized-geotiff-pyqgis",{"title":5,"description":1503},"spatial-data-processing-automation\u002Fweb-services-and-remote-data\u002Fread-cloud-optimized-geotiff-pyqgis\u002Findex","XvMGoUOXWApeNezswZqL2MioiWfjBfdABPkAolniWZc",1786789586256]