[{"data":1,"prerenderedAt":1965},["ShallowReactive",2],{"doc:\u002Fpyqgis-cartography-visualization\u002Fmap-canvas-and-image-export\u002Fgenerate-xyz-tiles-and-mbtiles-pyqgis":3},{"id":4,"title":5,"body":6,"description":1954,"extension":1955,"meta":1956,"navigation":238,"path":1961,"seo":1962,"stem":1963,"__hash__":1964},"docs\u002Fpyqgis-cartography-visualization\u002Fmap-canvas-and-image-export\u002Fgenerate-xyz-tiles-and-mbtiles-pyqgis\u002Findex.md","Generate XYZ Tiles and MBTiles in PyQGIS",{"type":7,"value":8,"toc":1941},"minimark",[9,13,22,31,156,161,178,182,185,779,789,793,802,1106,1138,1225,1229,1236,1253,1257,1260,1360,1598,1622,1626,1629,1763,1785,1789,1815,1819,1855,1859,1862,1866,1872,1887,1893,1903,1907,1937],[10,11,5],"h1",{"id":12},"generate-xyz-tiles-and-mbtiles-in-pyqgis",[14,15,16,17,21],"p",{},"A carefully styled QGIS map is only useful to people who have QGIS. Rendering it into a tile pyramid — thousands of small PNG or JPEG images at successive zoom levels, addressed as ",[18,19,20],"code",{},"z\u002Fx\u002Fy"," — turns it into something any web map library, mobile app or offline field tool can display. QGIS does the rendering with its own renderer, so labels, blending, symbol layers and everything else you styled come out exactly as they look on the canvas.",[14,23,24,25,30],{},"This recipe belongs to ",[26,27,29],"a",{"href":28},"\u002Fpyqgis-cartography-visualization\u002Fmap-canvas-and-image-export\u002F","Map Canvas Control and Image Export in PyQGIS",". It estimates the size of a tile job before starting it, generates a directory of tiles and an MBTiles file, controls which layers are drawn, avoids cut labels at tile edges, and loads the output back to check it.",[14,32,33],{},[34,35,40,44,48,55,64,72,79,95,98,104,108,113,117,123,127,133,137,143,147,152],"svg",{"viewBox":36,"role":37,"ariaLabel":38,"xmlns":39},"0 0 760 300","img","A tile pyramid: zoom 0 is one tile covering the world, each zoom level doubles the tiles in each direction, and a regional extent at zoom 16 needs tens of thousands of tiles addressed as z, x and y","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[41,42,43],"title",{},"Every zoom level quadruples the work",[45,46,47],"desc",{},"A stack of grids. Zoom 0: one 256 pixel tile for the whole Web Mercator world. Zoom 1: four tiles. Zoom 2: sixteen. For a city-sized extent, zoom 12 needs about 20 tiles, zoom 14 about 300, zoom 16 about 5,000 and zoom 18 about 80,000. Each tile is stored at a path z slash x slash y dot png, or as a row in an MBTiles SQLite file.",[49,50],"rect",{"x":51,"y":51,"width":52,"height":53,"fill":54},"0","760","300","#f6f3ea",[56,57,63],"text",{"x":58,"y":59,"style":60,"fill":61,"textAnchor":62},"380","28","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","Tiles per zoom level for one city",[49,65],{"x":66,"y":67,"width":68,"height":68,"fill":69,"stroke":70,"style":71},"60","222","36","#e8efe6","#15803d","stroke-width:1.5",[56,73,78],{"x":74,"y":75,"style":76,"fill":77,"textAnchor":62},"78","276","text-anchor:middle;font-size:10px;font-family:sans-serif","#2f3b35","z0 · 1",[80,81,83,87,90,93],"g",{"fill":69,"stroke":70,"style":82},"stroke-width:1",[49,84],{"x":85,"y":67,"width":86,"height":86},"130","18",[49,88],{"x":89,"y":67,"width":86,"height":86},"148",[49,91],{"x":85,"y":92,"width":86,"height":86},"240",[49,94],{"x":89,"y":92,"width":86,"height":86},[56,96,97],{"x":89,"y":75,"style":76,"fill":77,"textAnchor":62},"z1 · 4",[49,99],{"x":100,"y":101,"width":102,"height":103,"fill":70},"360","206","30","52",[56,105,107],{"x":106,"y":75,"style":76,"fill":77,"textAnchor":62},"375","z12 · 20",[49,109],{"x":110,"y":111,"width":102,"height":112,"fill":70},"440","170","88",[56,114,116],{"x":115,"y":75,"style":76,"fill":77,"textAnchor":62},"455","z14 · 300",[49,118],{"x":119,"y":120,"width":102,"height":121,"fill":122},"520","118","140","#b45309",[56,124,126],{"x":125,"y":75,"style":76,"fill":77,"textAnchor":62},"535","z16 · 5k",[49,128],{"x":129,"y":130,"width":102,"height":131,"fill":132},"600","54","204","#b91c1c",[56,134,136],{"x":135,"y":75,"style":76,"fill":77,"textAnchor":62},"615","z18 · 80k",[56,138,142],{"x":139,"y":140,"style":141,"fill":77,"textAnchor":62},"248","120","text-anchor:middle;font-size:10.5px;font-family:sans-serif","each level ×4",[56,144,146],{"x":139,"y":121,"style":145,"fill":77,"textAnchor":62},"text-anchor:middle;font-size:10px;font-family:monospace","tiles\u002Fz\u002Fx\u002Fy.png",[56,148,151],{"x":149,"y":150,"style":76,"fill":132,"textAnchor":62},"690","110","hours",[56,153,155],{"x":149,"y":154,"style":76,"fill":132,"textAnchor":62},"126","of render",[157,158,160],"h2",{"id":159},"prerequisites","Prerequisites",[162,163,164,172,175],"ul",{},[165,166,167,171],"li",{},[168,169,170],"strong",{},"QGIS 3.40 LTR"," or newer, or the QGIS 4 series.",[165,173,174],{},"A project styled the way the tiles should look. Tiles are rendered in Web Mercator (EPSG:3857); the project CRS does not need to be, but check the styling in 3857 first, because line widths and labels in map units change appearance with the projection.",[165,176,177],{},"Disk space for the output, and a realistic idea of the deepest zoom anyone needs.",[157,179,181],{"id":180},"estimate-the-job-before-running-it","Estimate the job before running it",[14,183,184],{},"Tile counts grow by a factor of four per zoom level, and render time grows with them. A few lines of arithmetic prevent starting a job that would run for a day.",[186,187,192],"pre",{"className":188,"code":189,"language":190,"meta":191,"style":191},"language-python shiki shiki-themes github-dark","import math\nfrom qgis.core import (\n    QgsProject, QgsCoordinateReferenceSystem, QgsCoordinateTransform, QgsRectangle,\n)\n\ndef tile_range(lon, lat, z):\n    n = 2 ** z\n    x = int((lon + 180.0) \u002F 360.0 * n)\n    lat_r = math.radians(max(min(lat, 85.0511), -85.0511))\n    y = int((1.0 - math.asinh(math.tan(lat_r)) \u002F math.pi) \u002F 2.0 * n)\n    return min(max(x, 0), n - 1), min(max(y, 0), n - 1)\n\ndef estimate(extent_4326, zmin, zmax, seconds_per_tile=0.08, kb_per_tile=18):\n    total = 0\n    for z in range(zmin, zmax + 1):\n        x0, y1 = tile_range(extent_4326.xMinimum(), extent_4326.yMinimum(), z)\n        x1, y0 = tile_range(extent_4326.xMaximum(), extent_4326.yMaximum(), z)\n        count = (x1 - x0 + 1) * (y1 - y0 + 1)\n        total += count\n        print(f\"z{z:>2}: {count:>8,} tiles\")\n    print(f\"total {total:,} tiles ≈ {total * seconds_per_tile \u002F 3600:.1f} h, \"\n          f\"{total * kb_per_tile \u002F 1024:.0f} MB\")\n    return total\n\narea = QgsProject.instance().mapLayersByName(\"city_boundary\")[0]\nto_4326 = QgsCoordinateTransform(area.crs(), QgsCoordinateReferenceSystem(\"EPSG:4326\"),\n                                 QgsProject.instance())\nextent = to_4326.transformBoundingBox(area.extent())\nestimate(extent, 10, 17)\n","python","",[18,193,194,207,221,227,233,240,253,272,307,344,380,427,432,458,469,493,504,515,554,566,611,660,693,701,706,728,745,751,762],{"__ignoreMap":191},[195,196,199,203],"span",{"class":197,"line":198},"line",1,[195,200,202],{"class":201},"snl16","import",[195,204,206],{"class":205},"s95oV"," math\n",[195,208,210,213,216,218],{"class":197,"line":209},2,[195,211,212],{"class":201},"from",[195,214,215],{"class":205}," qgis.core ",[195,217,202],{"class":201},[195,219,220],{"class":205}," (\n",[195,222,224],{"class":197,"line":223},3,[195,225,226],{"class":205},"    QgsProject, QgsCoordinateReferenceSystem, QgsCoordinateTransform, QgsRectangle,\n",[195,228,230],{"class":197,"line":229},4,[195,231,232],{"class":205},")\n",[195,234,236],{"class":197,"line":235},5,[195,237,239],{"emptyLinePlaceholder":238},true,"\n",[195,241,243,246,250],{"class":197,"line":242},6,[195,244,245],{"class":201},"def",[195,247,249],{"class":248},"svObZ"," tile_range",[195,251,252],{"class":205},"(lon, lat, z):\n",[195,254,256,259,262,266,269],{"class":197,"line":255},7,[195,257,258],{"class":205},"    n ",[195,260,261],{"class":201},"=",[195,263,265],{"class":264},"sDLfK"," 2",[195,267,268],{"class":201}," **",[195,270,271],{"class":205}," z\n",[195,273,275,278,280,283,286,289,292,295,298,301,304],{"class":197,"line":274},8,[195,276,277],{"class":205},"    x ",[195,279,261],{"class":201},[195,281,282],{"class":264}," int",[195,284,285],{"class":205},"((lon ",[195,287,288],{"class":201},"+",[195,290,291],{"class":264}," 180.0",[195,293,294],{"class":205},") ",[195,296,297],{"class":201},"\u002F",[195,299,300],{"class":264}," 360.0",[195,302,303],{"class":201}," *",[195,305,306],{"class":205}," n)\n",[195,308,310,313,315,318,321,324,327,330,333,336,339,341],{"class":197,"line":309},9,[195,311,312],{"class":205},"    lat_r ",[195,314,261],{"class":201},[195,316,317],{"class":205}," math.radians(",[195,319,320],{"class":264},"max",[195,322,323],{"class":205},"(",[195,325,326],{"class":264},"min",[195,328,329],{"class":205},"(lat, ",[195,331,332],{"class":264},"85.0511",[195,334,335],{"class":205},"), ",[195,337,338],{"class":201},"-",[195,340,332],{"class":264},[195,342,343],{"class":205},"))\n",[195,345,347,350,352,354,357,360,363,366,368,371,373,376,378],{"class":197,"line":346},10,[195,348,349],{"class":205},"    y ",[195,351,261],{"class":201},[195,353,282],{"class":264},[195,355,356],{"class":205},"((",[195,358,359],{"class":264},"1.0",[195,361,362],{"class":201}," -",[195,364,365],{"class":205}," math.asinh(math.tan(lat_r)) ",[195,367,297],{"class":201},[195,369,370],{"class":205}," math.pi) ",[195,372,297],{"class":201},[195,374,375],{"class":264}," 2.0",[195,377,303],{"class":201},[195,379,306],{"class":205},[195,381,383,386,389,391,393,396,398,401,403,406,408,410,412,414,417,419,421,423,425],{"class":197,"line":382},11,[195,384,385],{"class":201},"    return",[195,387,388],{"class":264}," min",[195,390,323],{"class":205},[195,392,320],{"class":264},[195,394,395],{"class":205},"(x, ",[195,397,51],{"class":264},[195,399,400],{"class":205},"), n ",[195,402,338],{"class":201},[195,404,405],{"class":264}," 1",[195,407,335],{"class":205},[195,409,326],{"class":264},[195,411,323],{"class":205},[195,413,320],{"class":264},[195,415,416],{"class":205},"(y, ",[195,418,51],{"class":264},[195,420,400],{"class":205},[195,422,338],{"class":201},[195,424,405],{"class":264},[195,426,232],{"class":205},[195,428,430],{"class":197,"line":429},12,[195,431,239],{"emptyLinePlaceholder":238},[195,433,435,437,440,443,445,448,451,453,455],{"class":197,"line":434},13,[195,436,245],{"class":201},[195,438,439],{"class":248}," estimate",[195,441,442],{"class":205},"(extent_4326, zmin, zmax, seconds_per_tile",[195,444,261],{"class":201},[195,446,447],{"class":264},"0.08",[195,449,450],{"class":205},", kb_per_tile",[195,452,261],{"class":201},[195,454,86],{"class":264},[195,456,457],{"class":205},"):\n",[195,459,461,464,466],{"class":197,"line":460},14,[195,462,463],{"class":205},"    total ",[195,465,261],{"class":201},[195,467,468],{"class":264}," 0\n",[195,470,472,475,478,481,484,487,489,491],{"class":197,"line":471},15,[195,473,474],{"class":201},"    for",[195,476,477],{"class":205}," z ",[195,479,480],{"class":201},"in",[195,482,483],{"class":264}," range",[195,485,486],{"class":205},"(zmin, zmax ",[195,488,288],{"class":201},[195,490,405],{"class":264},[195,492,457],{"class":205},[195,494,496,499,501],{"class":197,"line":495},16,[195,497,498],{"class":205},"        x0, y1 ",[195,500,261],{"class":201},[195,502,503],{"class":205}," tile_range(extent_4326.xMinimum(), extent_4326.yMinimum(), z)\n",[195,505,507,510,512],{"class":197,"line":506},17,[195,508,509],{"class":205},"        x1, y0 ",[195,511,261],{"class":201},[195,513,514],{"class":205}," tile_range(extent_4326.xMaximum(), extent_4326.yMaximum(), z)\n",[195,516,518,521,523,526,528,531,533,535,537,540,543,545,548,550,552],{"class":197,"line":517},18,[195,519,520],{"class":205},"        count ",[195,522,261],{"class":201},[195,524,525],{"class":205}," (x1 ",[195,527,338],{"class":201},[195,529,530],{"class":205}," x0 ",[195,532,288],{"class":201},[195,534,405],{"class":264},[195,536,294],{"class":205},[195,538,539],{"class":201},"*",[195,541,542],{"class":205}," (y1 ",[195,544,338],{"class":201},[195,546,547],{"class":205}," y0 ",[195,549,288],{"class":201},[195,551,405],{"class":264},[195,553,232],{"class":205},[195,555,557,560,563],{"class":197,"line":556},19,[195,558,559],{"class":205},"        total ",[195,561,562],{"class":201},"+=",[195,564,565],{"class":205}," count\n",[195,567,569,572,574,577,581,584,587,590,593,596,598,601,604,606,609],{"class":197,"line":568},20,[195,570,571],{"class":264},"        print",[195,573,323],{"class":205},[195,575,576],{"class":201},"f",[195,578,580],{"class":579},"sU2Wk","\"z",[195,582,583],{"class":264},"{",[195,585,586],{"class":205},"z",[195,588,589],{"class":201},":>2",[195,591,592],{"class":264},"}",[195,594,595],{"class":579},": ",[195,597,583],{"class":264},[195,599,600],{"class":205},"count",[195,602,603],{"class":201},":>8,",[195,605,592],{"class":264},[195,607,608],{"class":579}," tiles\"",[195,610,232],{"class":205},[195,612,614,617,619,621,624,626,629,632,634,637,639,642,644,647,649,652,655,657],{"class":197,"line":613},21,[195,615,616],{"class":264},"    print",[195,618,323],{"class":205},[195,620,576],{"class":201},[195,622,623],{"class":579},"\"total ",[195,625,583],{"class":264},[195,627,628],{"class":205},"total",[195,630,631],{"class":201},":,",[195,633,592],{"class":264},[195,635,636],{"class":579}," tiles ≈ ",[195,638,583],{"class":264},[195,640,641],{"class":205},"total ",[195,643,539],{"class":201},[195,645,646],{"class":205}," seconds_per_tile ",[195,648,297],{"class":201},[195,650,651],{"class":264}," 3600",[195,653,654],{"class":201},":.1f",[195,656,592],{"class":264},[195,658,659],{"class":579}," h, \"\n",[195,661,663,666,669,671,673,675,678,680,683,686,688,691],{"class":197,"line":662},22,[195,664,665],{"class":201},"          f",[195,667,668],{"class":579},"\"",[195,670,583],{"class":264},[195,672,641],{"class":205},[195,674,539],{"class":201},[195,676,677],{"class":205}," kb_per_tile ",[195,679,297],{"class":201},[195,681,682],{"class":264}," 1024",[195,684,685],{"class":201},":.0f",[195,687,592],{"class":264},[195,689,690],{"class":579}," MB\"",[195,692,232],{"class":205},[195,694,696,698],{"class":197,"line":695},23,[195,697,385],{"class":201},[195,699,700],{"class":205}," total\n",[195,702,704],{"class":197,"line":703},24,[195,705,239],{"emptyLinePlaceholder":238},[195,707,709,712,714,717,720,723,725],{"class":197,"line":708},25,[195,710,711],{"class":205},"area ",[195,713,261],{"class":201},[195,715,716],{"class":205}," QgsProject.instance().mapLayersByName(",[195,718,719],{"class":579},"\"city_boundary\"",[195,721,722],{"class":205},")[",[195,724,51],{"class":264},[195,726,727],{"class":205},"]\n",[195,729,731,734,736,739,742],{"class":197,"line":730},26,[195,732,733],{"class":205},"to_4326 ",[195,735,261],{"class":201},[195,737,738],{"class":205}," QgsCoordinateTransform(area.crs(), QgsCoordinateReferenceSystem(",[195,740,741],{"class":579},"\"EPSG:4326\"",[195,743,744],{"class":205},"),\n",[195,746,748],{"class":197,"line":747},27,[195,749,750],{"class":205},"                                 QgsProject.instance())\n",[195,752,754,757,759],{"class":197,"line":753},28,[195,755,756],{"class":205},"extent ",[195,758,261],{"class":201},[195,760,761],{"class":205}," to_4326.transformBoundingBox(area.extent())\n",[195,763,765,768,771,774,777],{"class":197,"line":764},29,[195,766,767],{"class":205},"estimate(extent, ",[195,769,770],{"class":264},"10",[195,772,773],{"class":205},", ",[195,775,776],{"class":264},"17",[195,778,232],{"class":205},[14,780,781,784,785,788],{},[168,782,783],{},"Breakdown:"," The tile arithmetic is the standard slippy-map formula, clamped to the latitude limit of Web Mercator. Render time per tile depends entirely on the project — a simple vector map renders dozens of tiles per second, a map with heavy labelling and a hillshade far fewer — so time a small run at your deepest zoom and put the real number into ",[18,786,787],{},"seconds_per_tile",". Each extra zoom level roughly quadruples the total, which is why stopping one level earlier is the single biggest saving available.",[157,790,792],{"id":791},"render-tiles-to-a-directory","Render tiles to a directory",[14,794,795,798,799,801],{},[18,796,797],{},"native:tilesxyzdirectory"," writes a ",[18,800,20],{}," folder tree and, optionally, a small HTML viewer. It renders the layers visible in the project, in their current style.",[186,803,805],{"className":188,"code":804,"language":190,"meta":191,"style":191},"import processing\nfrom qgis.core import QgsProcessingFeedback\n\nclass PrintFeedback(QgsProcessingFeedback):\n    def setProgress(self, p):\n        super().setProgress(p)\n        print(f\"\\r{p:5.1f}%\", end=\"\")\n\nresult = processing.run(\"native:tilesxyzdirectory\", {\n    \"EXTENT\": area.extent(),\n    \"ZOOM_MIN\": 10,\n    \"ZOOM_MAX\": 16,\n    \"DPI\": 96,\n    \"BACKGROUND_COLOR\": None,\n    \"TILE_FORMAT\": 0,\n    \"QUALITY\": 75,\n    \"METATILESIZE\": 4,\n    \"TILE_WIDTH\": 256,\n    \"TILE_HEIGHT\": 256,\n    \"TMS_CONVENTION\": False,\n    \"OUTPUT_DIRECTORY\": \"\u002Fsrv\u002Ftiles\u002Fcity_basemap\",\n    \"OUTPUT_HTML\": \"\u002Fsrv\u002Ftiles\u002Fcity_basemap\u002Findex.html\",\n}, feedback=PrintFeedback())\nprint(\"\\ndone:\", result[\"OUTPUT_DIRECTORY\"])\n",[18,806,807,814,825,829,844,855,863,899,903,919,927,939,951,963,975,986,998,1010,1022,1033,1045,1057,1069,1082],{"__ignoreMap":191},[195,808,809,811],{"class":197,"line":198},[195,810,202],{"class":201},[195,812,813],{"class":205}," processing\n",[195,815,816,818,820,822],{"class":197,"line":209},[195,817,212],{"class":201},[195,819,215],{"class":205},[195,821,202],{"class":201},[195,823,824],{"class":205}," QgsProcessingFeedback\n",[195,826,827],{"class":197,"line":223},[195,828,239],{"emptyLinePlaceholder":238},[195,830,831,834,837,839,842],{"class":197,"line":229},[195,832,833],{"class":201},"class",[195,835,836],{"class":248}," PrintFeedback",[195,838,323],{"class":205},[195,840,841],{"class":248},"QgsProcessingFeedback",[195,843,457],{"class":205},[195,845,846,849,852],{"class":197,"line":235},[195,847,848],{"class":201},"    def",[195,850,851],{"class":248}," setProgress",[195,853,854],{"class":205},"(self, p):\n",[195,856,857,860],{"class":197,"line":242},[195,858,859],{"class":264},"        super",[195,861,862],{"class":205},"().setProgress(p)\n",[195,864,865,867,869,871,873,876,878,881,883,886,888,892,894,897],{"class":197,"line":255},[195,866,571],{"class":264},[195,868,323],{"class":205},[195,870,576],{"class":201},[195,872,668],{"class":579},[195,874,875],{"class":264},"\\r{",[195,877,14],{"class":205},[195,879,880],{"class":201},":5.1f",[195,882,592],{"class":264},[195,884,885],{"class":579},"%\"",[195,887,773],{"class":205},[195,889,891],{"class":890},"s9osk","end",[195,893,261],{"class":201},[195,895,896],{"class":579},"\"\"",[195,898,232],{"class":205},[195,900,901],{"class":197,"line":274},[195,902,239],{"emptyLinePlaceholder":238},[195,904,905,908,910,913,916],{"class":197,"line":309},[195,906,907],{"class":205},"result ",[195,909,261],{"class":201},[195,911,912],{"class":205}," processing.run(",[195,914,915],{"class":579},"\"native:tilesxyzdirectory\"",[195,917,918],{"class":205},", {\n",[195,920,921,924],{"class":197,"line":346},[195,922,923],{"class":579},"    \"EXTENT\"",[195,925,926],{"class":205},": area.extent(),\n",[195,928,929,932,934,936],{"class":197,"line":382},[195,930,931],{"class":579},"    \"ZOOM_MIN\"",[195,933,595],{"class":205},[195,935,770],{"class":264},[195,937,938],{"class":205},",\n",[195,940,941,944,946,949],{"class":197,"line":429},[195,942,943],{"class":579},"    \"ZOOM_MAX\"",[195,945,595],{"class":205},[195,947,948],{"class":264},"16",[195,950,938],{"class":205},[195,952,953,956,958,961],{"class":197,"line":434},[195,954,955],{"class":579},"    \"DPI\"",[195,957,595],{"class":205},[195,959,960],{"class":264},"96",[195,962,938],{"class":205},[195,964,965,968,970,973],{"class":197,"line":460},[195,966,967],{"class":579},"    \"BACKGROUND_COLOR\"",[195,969,595],{"class":205},[195,971,972],{"class":264},"None",[195,974,938],{"class":205},[195,976,977,980,982,984],{"class":197,"line":471},[195,978,979],{"class":579},"    \"TILE_FORMAT\"",[195,981,595],{"class":205},[195,983,51],{"class":264},[195,985,938],{"class":205},[195,987,988,991,993,996],{"class":197,"line":495},[195,989,990],{"class":579},"    \"QUALITY\"",[195,992,595],{"class":205},[195,994,995],{"class":264},"75",[195,997,938],{"class":205},[195,999,1000,1003,1005,1008],{"class":197,"line":506},[195,1001,1002],{"class":579},"    \"METATILESIZE\"",[195,1004,595],{"class":205},[195,1006,1007],{"class":264},"4",[195,1009,938],{"class":205},[195,1011,1012,1015,1017,1020],{"class":197,"line":517},[195,1013,1014],{"class":579},"    \"TILE_WIDTH\"",[195,1016,595],{"class":205},[195,1018,1019],{"class":264},"256",[195,1021,938],{"class":205},[195,1023,1024,1027,1029,1031],{"class":197,"line":556},[195,1025,1026],{"class":579},"    \"TILE_HEIGHT\"",[195,1028,595],{"class":205},[195,1030,1019],{"class":264},[195,1032,938],{"class":205},[195,1034,1035,1038,1040,1043],{"class":197,"line":568},[195,1036,1037],{"class":579},"    \"TMS_CONVENTION\"",[195,1039,595],{"class":205},[195,1041,1042],{"class":264},"False",[195,1044,938],{"class":205},[195,1046,1047,1050,1052,1055],{"class":197,"line":613},[195,1048,1049],{"class":579},"    \"OUTPUT_DIRECTORY\"",[195,1051,595],{"class":205},[195,1053,1054],{"class":579},"\"\u002Fsrv\u002Ftiles\u002Fcity_basemap\"",[195,1056,938],{"class":205},[195,1058,1059,1062,1064,1067],{"class":197,"line":662},[195,1060,1061],{"class":579},"    \"OUTPUT_HTML\"",[195,1063,595],{"class":205},[195,1065,1066],{"class":579},"\"\u002Fsrv\u002Ftiles\u002Fcity_basemap\u002Findex.html\"",[195,1068,938],{"class":205},[195,1070,1071,1074,1077,1079],{"class":197,"line":695},[195,1072,1073],{"class":205},"}, ",[195,1075,1076],{"class":890},"feedback",[195,1078,261],{"class":201},[195,1080,1081],{"class":205},"PrintFeedback())\n",[195,1083,1084,1087,1089,1091,1094,1097,1100,1103],{"class":197,"line":703},[195,1085,1086],{"class":264},"print",[195,1088,323],{"class":205},[195,1090,668],{"class":579},[195,1092,1093],{"class":264},"\\n",[195,1095,1096],{"class":579},"done:\"",[195,1098,1099],{"class":205},", result[",[195,1101,1102],{"class":579},"\"OUTPUT_DIRECTORY\"",[195,1104,1105],{"class":205},"])\n",[14,1107,1108,1110,1111,1114,1115,1118,1119,1122,1123,1126,1127,1129,1130,1133,1134,1137],{},[168,1109,783],{}," ",[18,1112,1113],{},"EXTENT"," accepts a rectangle in the layer's CRS and is reprojected internally. ",[18,1116,1117],{},"TILE_FORMAT"," 0 is PNG, which supports transparency and suits vector cartography; 1 is JPEG, far smaller for imagery and hillshades but with no transparency, which is why ",[18,1120,1121],{},"QUALITY"," only applies to it. A ",[18,1124,1125],{},"BACKGROUND_COLOR"," of ",[18,1128,972],{}," keeps transparent backgrounds so the tiles can overlay another basemap. ",[18,1131,1132],{},"TMS_CONVENTION"," flips the y axis for the older TMS scheme; leave it false for the XYZ convention every modern web library uses. Parameter names occasionally gain additions between releases, so print ",[18,1135,1136],{},"processing.algorithmHelp(\"native:tilesxyzdirectory\")"," once on your version.",[14,1139,1140],{},[34,1141,1144,1147,1150,1152,1156,1164,1170,1180,1185,1191,1195,1199,1202,1207,1211,1215,1218,1222],{"viewBox":1142,"role":37,"ariaLabel":1143,"xmlns":39},"0 0 760 256","Why metatiles matter for labels: rendering each 256 pixel tile separately cuts or duplicates labels at tile edges, while rendering a 4 by 4 metatile and slicing it keeps labels whole and positioned once",[41,1145,1146],{},"Render big, slice small",[45,1148,1149],{},"Left: four tiles rendered individually. A street label near the shared edge is placed in both tiles and cut by the boundary, so it appears twice and truncated. Right: the same area rendered once as a single metatile of 4 by 4 tiles, the label placed once in the middle, then sliced into tiles so each tile contains its part of the single label.",[49,1151],{"x":51,"y":51,"width":52,"height":1019,"fill":54},[56,1153,1155],{"x":58,"y":1154,"style":60,"fill":61,"textAnchor":62},"26","Labels cross tile edges; render across them",[49,1157],{"x":1158,"y":1159,"width":1160,"height":1161,"rx":770,"fill":1162,"stroke":132,"style":1163},"24","44","344","192","#fffdf7","stroke-width:2.5",[56,1165,1169],{"x":1166,"y":1167,"style":1168,"fill":132,"textAnchor":62},"196","68","text-anchor:middle;font-size:11.5px;font-weight:bold;font-family:sans-serif","tile by tile",[80,1171,1173,1178],{"stroke":1172,"style":71},"#9aa39d",[49,1174],{"x":960,"y":1175,"width":1176,"height":1176,"fill":1177},"84","100","#fbf8f0",[49,1179],{"x":1166,"y":1175,"width":1176,"height":1176,"fill":1177},[197,1181],{"x1":960,"y1":121,"x2":1182,"y2":121,"stroke":1183,"style":1184},"296","#59645f","stroke-width:4",[56,1186,1190],{"x":1187,"y":1188,"style":1189,"fill":77},"186","132","text-anchor:end;font-size:11px;font-weight:bold;font-family:sans-serif","Mill Stre",[56,1192,1194],{"x":101,"y":1188,"style":1193,"fill":77},"font-size:11px;font-weight:bold;font-family:sans-serif","treet",[56,1196,1198],{"x":1166,"y":1197,"style":76,"fill":77,"textAnchor":62},"212","cut and doubled at the edge",[49,1200],{"x":1201,"y":1159,"width":1160,"height":1161,"rx":770,"fill":1162,"stroke":70,"style":1163},"392",[56,1203,1206],{"x":1204,"y":1167,"style":1168,"fill":1205,"textAnchor":62},"564","#166534","metatile, then slice",[49,1208],{"x":1209,"y":1175,"width":1210,"height":1176,"fill":1177,"stroke":70,"style":71},"464","200",[197,1212],{"x1":1204,"y1":1175,"x2":1204,"y2":1213,"stroke":1172,"style":1214},"184","stroke-width:1;stroke-dasharray:4 3",[197,1216],{"x1":1209,"y1":121,"x2":1217,"y2":121,"stroke":1183,"style":1184},"664",[56,1219,1221],{"x":1204,"y":1188,"style":1220,"fill":77,"textAnchor":62},"text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif","Mill Street",[56,1223,1224],{"x":1204,"y":1197,"style":76,"fill":77,"textAnchor":62},"placed once, sliced cleanly",[157,1226,1228],{"id":1227},"clean-labels-with-metatiles","Clean labels with metatiles",[14,1230,1231,1232,1235],{},"Rendering each 256-pixel tile on its own makes the labelling engine place labels per tile, so a street name near an edge is cut in half or appears in two neighbouring tiles. ",[18,1233,1234],{},"METATILESIZE"," renders a block of tiles as one image and slices it, so labels are placed across tile boundaries.",[14,1237,1238,1239,1243,1244,1247,1248,1252],{},"A metatile size of 4 renders 1024 × 1024 pixel images and slices them into sixteen tiles, which fixes almost every edge artefact and is usually faster overall because the renderer's setup cost is shared. Larger sizes use more memory per render; 8 is reasonable for label-heavy maps, and anything above 20 rarely helps. Labels can still be cut at ",[1240,1241,1242],"em",{},"metatile"," boundaries, so in the project's label settings turn off ",[1240,1245,1246],{},"Show partial labels"," and give important label layers a little priority so they are placed away from edges where possible — the placement controls from ",[26,1249,1251],{"href":1250},"\u002Fpyqgis-cartography-visualization\u002Flabeling-and-annotations\u002Fcontrol-label-placement-and-collisions-pyqgis\u002F","controlling label placement and collisions"," apply unchanged.",[157,1254,1256],{"id":1255},"choose-which-layers-are-drawn","Choose which layers are drawn",[14,1258,1259],{},"The tile algorithms draw the layers visible in the project. Rather than toggling visibility by hand before every run, apply a map theme that defines exactly the tile basemap, render, and restore the user's view.",[14,1261,1262],{},[34,1263,1266,1269,1272,1275,1291,1294,1298,1303,1306,1311,1316,1320,1323,1327,1332,1336,1339,1342,1346,1350,1353,1357],{"viewBox":1264,"role":37,"ariaLabel":1265,"xmlns":39},"0 0 760 236","Using a map theme to control tile content: save the current visibility, apply the tiles theme with only basemap layers visible, render tiles, then restore the saved state so the user's project is unchanged",[41,1267,1268],{},"Theme in, render, theme out",[45,1270,1271],{},"Four steps. Capture the current layer visibility as a temporary theme. Apply the tile_basemap theme, which shows roads, buildings, water and labels but hides working layers such as survey points and analysis results. Run the tile algorithm. Re-apply the captured theme so the project looks exactly as before.",[49,1273],{"x":51,"y":51,"width":52,"height":1274,"fill":54},"236",[1276,1277,1278],"defs",{},[1279,1280,1287],"marker",{"id":1281,"viewBox":1282,"refX":1283,"refY":1284,"markerWidth":1285,"markerHeight":1285,"orient":1286},"xyzThemeArrow","0 0 10 10","8","5","7","auto-start-reverse",[1288,1289],"path",{"d":1290,"fill":77},"M0 0 L10 5 L0 10 z",[56,1292,1293],{"x":58,"y":1154,"style":60,"fill":61,"textAnchor":62},"Render a defined map, leave the project untouched",[49,1295],{"x":1158,"y":66,"width":1296,"height":960,"rx":770,"fill":1162,"stroke":1183,"style":1297},"160","stroke-width:2",[56,1299,1302],{"x":1300,"y":960,"style":1301,"fill":61,"textAnchor":62},"104","text-anchor:middle;font-size:10.5px;font-weight:bold;font-family:sans-serif","capture",[56,1304,1305],{"x":1300,"y":120,"style":76,"fill":77,"textAnchor":62},"current visibility",[197,1307],{"x1":1213,"y1":1308,"x2":1309,"y2":1308,"stroke":77,"style":1310},"108","208","stroke-width:1.8;marker-end:url(#xyzThemeArrow)",[49,1312],{"x":1313,"y":66,"width":1296,"height":960,"rx":770,"fill":1314,"stroke":1315,"style":1297},"214","#eef7f4","#0f766e",[56,1317,1319],{"x":1318,"y":960,"style":1301,"fill":1315,"textAnchor":62},"294","apply theme",[56,1321,1322],{"x":1318,"y":120,"style":145,"fill":77,"textAnchor":62},"tile_basemap",[197,1324],{"x1":1325,"y1":1308,"x2":1326,"y2":1308,"stroke":77,"style":1310},"374","398",[49,1328],{"x":1329,"y":66,"width":1296,"height":960,"rx":770,"fill":1330,"stroke":1331,"style":1297},"404","#eff3ff","#2563eb",[56,1333,1335],{"x":1334,"y":960,"style":1301,"fill":1331,"textAnchor":62},"484","render tiles",[56,1337,1338],{"x":1334,"y":120,"style":76,"fill":77,"textAnchor":62},"visible layers only",[197,1340],{"x1":1204,"y1":1308,"x2":1341,"y2":1308,"stroke":77,"style":1310},"588",[49,1343],{"x":1344,"y":66,"width":1345,"height":960,"rx":770,"fill":69,"stroke":70,"style":1297},"594","142",[56,1347,1349],{"x":1348,"y":960,"style":1301,"fill":1205,"textAnchor":62},"665","restore",[56,1351,1352],{"x":1348,"y":120,"style":76,"fill":77,"textAnchor":62},"as it was",[49,1354],{"x":140,"y":1213,"width":119,"height":1355,"rx":1283,"fill":1162,"stroke":1183,"style":1356},"34","stroke-width:1.8",[56,1358,1359],{"x":58,"y":101,"style":141,"fill":77,"textAnchor":62},"wrap in try\u002Ffinally so a failed render still restores the view",[186,1361,1363],{"className":188,"code":1362,"language":190,"meta":191,"style":191},"from qgis.core import QgsMapThemeCollection\n\nproject = QgsProject.instance()\nthemes = project.mapThemeCollection()\nroot, model = project.layerTreeRoot(), iface.layerTreeView().layerTreeModel()\n\nsaved = QgsMapThemeCollection.createThemeFromCurrentState(root, model)\nthemes.insert(\"_before_tiles\", saved)\ntry:\n    themes.applyTheme(\"tile_basemap\", root, model)\n    processing.run(\"native:tilesxyzmbtiles\", {\n        \"EXTENT\": area.extent(), \"ZOOM_MIN\": 10, \"ZOOM_MAX\": 16, \"DPI\": 96,\n        \"BACKGROUND_COLOR\": None, \"TILE_FORMAT\": 0, \"QUALITY\": 75,\n        \"METATILESIZE\": 4, \"TILE_WIDTH\": 256, \"TILE_HEIGHT\": 256,\n        \"OUTPUT_FILE\": \"\u002Fsrv\u002Ftiles\u002Fcity_basemap.mbtiles\",\n    })\nfinally:\n    themes.applyTheme(\"_before_tiles\", root, model)\n    themes.removeMapTheme(\"_before_tiles\")\n",[18,1364,1365,1376,1380,1390,1400,1410,1414,1424,1435,1443,1454,1464,1499,1528,1557,1569,1574,1581,1589],{"__ignoreMap":191},[195,1366,1367,1369,1371,1373],{"class":197,"line":198},[195,1368,212],{"class":201},[195,1370,215],{"class":205},[195,1372,202],{"class":201},[195,1374,1375],{"class":205}," QgsMapThemeCollection\n",[195,1377,1378],{"class":197,"line":209},[195,1379,239],{"emptyLinePlaceholder":238},[195,1381,1382,1385,1387],{"class":197,"line":223},[195,1383,1384],{"class":205},"project ",[195,1386,261],{"class":201},[195,1388,1389],{"class":205}," QgsProject.instance()\n",[195,1391,1392,1395,1397],{"class":197,"line":229},[195,1393,1394],{"class":205},"themes ",[195,1396,261],{"class":201},[195,1398,1399],{"class":205}," project.mapThemeCollection()\n",[195,1401,1402,1405,1407],{"class":197,"line":235},[195,1403,1404],{"class":205},"root, model ",[195,1406,261],{"class":201},[195,1408,1409],{"class":205}," project.layerTreeRoot(), iface.layerTreeView().layerTreeModel()\n",[195,1411,1412],{"class":197,"line":242},[195,1413,239],{"emptyLinePlaceholder":238},[195,1415,1416,1419,1421],{"class":197,"line":255},[195,1417,1418],{"class":205},"saved ",[195,1420,261],{"class":201},[195,1422,1423],{"class":205}," QgsMapThemeCollection.createThemeFromCurrentState(root, model)\n",[195,1425,1426,1429,1432],{"class":197,"line":274},[195,1427,1428],{"class":205},"themes.insert(",[195,1430,1431],{"class":579},"\"_before_tiles\"",[195,1433,1434],{"class":205},", saved)\n",[195,1436,1437,1440],{"class":197,"line":309},[195,1438,1439],{"class":201},"try",[195,1441,1442],{"class":205},":\n",[195,1444,1445,1448,1451],{"class":197,"line":346},[195,1446,1447],{"class":205},"    themes.applyTheme(",[195,1449,1450],{"class":579},"\"tile_basemap\"",[195,1452,1453],{"class":205},", root, model)\n",[195,1455,1456,1459,1462],{"class":197,"line":382},[195,1457,1458],{"class":205},"    processing.run(",[195,1460,1461],{"class":579},"\"native:tilesxyzmbtiles\"",[195,1463,918],{"class":205},[195,1465,1466,1469,1472,1475,1477,1479,1481,1484,1486,1488,1490,1493,1495,1497],{"class":197,"line":429},[195,1467,1468],{"class":579},"        \"EXTENT\"",[195,1470,1471],{"class":205},": area.extent(), ",[195,1473,1474],{"class":579},"\"ZOOM_MIN\"",[195,1476,595],{"class":205},[195,1478,770],{"class":264},[195,1480,773],{"class":205},[195,1482,1483],{"class":579},"\"ZOOM_MAX\"",[195,1485,595],{"class":205},[195,1487,948],{"class":264},[195,1489,773],{"class":205},[195,1491,1492],{"class":579},"\"DPI\"",[195,1494,595],{"class":205},[195,1496,960],{"class":264},[195,1498,938],{"class":205},[195,1500,1501,1504,1506,1508,1510,1513,1515,1517,1519,1522,1524,1526],{"class":197,"line":434},[195,1502,1503],{"class":579},"        \"BACKGROUND_COLOR\"",[195,1505,595],{"class":205},[195,1507,972],{"class":264},[195,1509,773],{"class":205},[195,1511,1512],{"class":579},"\"TILE_FORMAT\"",[195,1514,595],{"class":205},[195,1516,51],{"class":264},[195,1518,773],{"class":205},[195,1520,1521],{"class":579},"\"QUALITY\"",[195,1523,595],{"class":205},[195,1525,995],{"class":264},[195,1527,938],{"class":205},[195,1529,1530,1533,1535,1537,1539,1542,1544,1546,1548,1551,1553,1555],{"class":197,"line":460},[195,1531,1532],{"class":579},"        \"METATILESIZE\"",[195,1534,595],{"class":205},[195,1536,1007],{"class":264},[195,1538,773],{"class":205},[195,1540,1541],{"class":579},"\"TILE_WIDTH\"",[195,1543,595],{"class":205},[195,1545,1019],{"class":264},[195,1547,773],{"class":205},[195,1549,1550],{"class":579},"\"TILE_HEIGHT\"",[195,1552,595],{"class":205},[195,1554,1019],{"class":264},[195,1556,938],{"class":205},[195,1558,1559,1562,1564,1567],{"class":197,"line":471},[195,1560,1561],{"class":579},"        \"OUTPUT_FILE\"",[195,1563,595],{"class":205},[195,1565,1566],{"class":579},"\"\u002Fsrv\u002Ftiles\u002Fcity_basemap.mbtiles\"",[195,1568,938],{"class":205},[195,1570,1571],{"class":197,"line":495},[195,1572,1573],{"class":205},"    })\n",[195,1575,1576,1579],{"class":197,"line":506},[195,1577,1578],{"class":201},"finally",[195,1580,1442],{"class":205},[195,1582,1583,1585,1587],{"class":197,"line":517},[195,1584,1447],{"class":205},[195,1586,1431],{"class":579},[195,1588,1453],{"class":205},[195,1590,1591,1594,1596],{"class":197,"line":556},[195,1592,1593],{"class":205},"    themes.removeMapTheme(",[195,1595,1431],{"class":579},[195,1597,232],{"class":205},[14,1599,1600,1602,1603,1605,1606,1608,1609,1613,1614,1617,1618,1621],{},[168,1601,783],{}," Capturing the current state as a temporary theme and restoring it in ",[18,1604,1578],{}," leaves the project exactly as it was, even if rendering fails half-way. The ",[18,1607,1322],{}," theme itself is created once, by hand or in code, as described in ",[26,1610,1612],{"href":1611},"\u002Fpyqgis-cartography-visualization\u002Fmap-themes-and-layer-visibility\u002Fcreate-map-theme-pyqgis\u002F","creating a map theme",". ",[18,1615,1616],{},"native:tilesxyzmbtiles"," takes the same rendering parameters and writes a single SQLite file instead of a directory tree — much easier to copy, sync to a mobile device or serve, because it is one file instead of hundreds of thousands. This snippet uses ",[18,1619,1620],{},"iface",", so it runs in QGIS Desktop; in a headless script, set visibility on the layer tree nodes directly instead.",[157,1623,1625],{"id":1624},"check-the-output","Check the output",[14,1627,1628],{},"Load the result back into QGIS to confirm it looks right at a few zoom levels before publishing it.",[186,1630,1632],{"className":188,"code":1631,"language":190,"meta":191,"style":191},"from qgis.core import QgsRasterLayer\n\nmbtiles = QgsRasterLayer(\"\u002Fsrv\u002Ftiles\u002Fcity_basemap.mbtiles\", \"tiles (mbtiles)\", \"gdal\")\nprint(\"mbtiles valid:\", mbtiles.isValid(), mbtiles.extent())\n\nxyz_uri = (\"type=xyz&url=file:\u002F\u002F\u002Fsrv\u002Ftiles\u002Fcity_basemap\u002F%7Bz%7D\u002F%7Bx%7D\u002F%7By%7D.png\"\n           \"&zmin=10&zmax=16\")\ndirectory = QgsRasterLayer(xyz_uri, \"tiles (directory)\", \"wms\")\nprint(\"directory valid:\", directory.isValid())\n\nfor layer in (mbtiles, directory):\n    QgsProject.instance().addMapLayer(layer)\n",[18,1633,1634,1645,1649,1673,1685,1689,1702,1709,1729,1741,1745,1758],{"__ignoreMap":191},[195,1635,1636,1638,1640,1642],{"class":197,"line":198},[195,1637,212],{"class":201},[195,1639,215],{"class":205},[195,1641,202],{"class":201},[195,1643,1644],{"class":205}," QgsRasterLayer\n",[195,1646,1647],{"class":197,"line":209},[195,1648,239],{"emptyLinePlaceholder":238},[195,1650,1651,1654,1656,1659,1661,1663,1666,1668,1671],{"class":197,"line":223},[195,1652,1653],{"class":205},"mbtiles ",[195,1655,261],{"class":201},[195,1657,1658],{"class":205}," QgsRasterLayer(",[195,1660,1566],{"class":579},[195,1662,773],{"class":205},[195,1664,1665],{"class":579},"\"tiles (mbtiles)\"",[195,1667,773],{"class":205},[195,1669,1670],{"class":579},"\"gdal\"",[195,1672,232],{"class":205},[195,1674,1675,1677,1679,1682],{"class":197,"line":229},[195,1676,1086],{"class":264},[195,1678,323],{"class":205},[195,1680,1681],{"class":579},"\"mbtiles valid:\"",[195,1683,1684],{"class":205},", mbtiles.isValid(), mbtiles.extent())\n",[195,1686,1687],{"class":197,"line":235},[195,1688,239],{"emptyLinePlaceholder":238},[195,1690,1691,1694,1696,1699],{"class":197,"line":242},[195,1692,1693],{"class":205},"xyz_uri ",[195,1695,261],{"class":201},[195,1697,1698],{"class":205}," (",[195,1700,1701],{"class":579},"\"type=xyz&url=file:\u002F\u002F\u002Fsrv\u002Ftiles\u002Fcity_basemap\u002F%7Bz%7D\u002F%7Bx%7D\u002F%7By%7D.png\"\n",[195,1703,1704,1707],{"class":197,"line":255},[195,1705,1706],{"class":579},"           \"&zmin=10&zmax=16\"",[195,1708,232],{"class":205},[195,1710,1711,1714,1716,1719,1722,1724,1727],{"class":197,"line":274},[195,1712,1713],{"class":205},"directory ",[195,1715,261],{"class":201},[195,1717,1718],{"class":205}," QgsRasterLayer(xyz_uri, ",[195,1720,1721],{"class":579},"\"tiles (directory)\"",[195,1723,773],{"class":205},[195,1725,1726],{"class":579},"\"wms\"",[195,1728,232],{"class":205},[195,1730,1731,1733,1735,1738],{"class":197,"line":309},[195,1732,1086],{"class":264},[195,1734,323],{"class":205},[195,1736,1737],{"class":579},"\"directory valid:\"",[195,1739,1740],{"class":205},", directory.isValid())\n",[195,1742,1743],{"class":197,"line":346},[195,1744,239],{"emptyLinePlaceholder":238},[195,1746,1747,1750,1753,1755],{"class":197,"line":382},[195,1748,1749],{"class":201},"for",[195,1751,1752],{"class":205}," layer ",[195,1754,480],{"class":201},[195,1756,1757],{"class":205}," (mbtiles, directory):\n",[195,1759,1760],{"class":197,"line":429},[195,1761,1762],{"class":205},"    QgsProject.instance().addMapLayer(layer)\n",[14,1764,1765,1767,1768,1771,1772,1775,1776,1780,1781,1784],{},[168,1766,783],{}," GDAL reads MBTiles directly as a raster. A directory of tiles loads through the XYZ connection type of the ",[18,1769,1770],{},"wms"," provider with a ",[18,1773,1774],{},"file:\u002F\u002F"," URL template; the placeholders are percent-encoded because the whole string is itself a URI — the same pattern used for online basemaps in ",[26,1777,1779],{"href":1778},"\u002Fspatial-data-processing-automation\u002Fweb-services-and-remote-data\u002Fadd-xyz-tile-basemap-pyqgis\u002F","adding an XYZ tile basemap",". Setting ",[18,1782,1783],{},"zmax"," stops QGIS requesting tiles deeper than those generated, which would otherwise show as blank. Zoom to the edges of the extent and to dense label areas at the highest zoom; that is where problems appear.",[157,1786,1788],{"id":1787},"qgis-version-compatibility","QGIS version compatibility",[14,1790,1791,1793,1794,1796,1797,1793,1800,1803,1804,1806,1807,1810,1811,1814],{},[18,1792,797],{}," and ",[18,1795,1616],{}," have existed since QGIS 3.8. ",[18,1798,1799],{},"TILE_WIDTH",[18,1801,1802],{},"TILE_HEIGHT"," were added in 3.10 for high-DPI tiles, and ",[18,1805,1132],{}," in 3.12. Later releases added options such as antialiasing control; unknown parameters are ignored and missing ones take defaults, so check ",[18,1808,1809],{},"algorithmHelp"," on your version. For vector tiles rather than rendered images, ",[18,1812,1813],{},"native:writevectortiles_mbtiles"," is the separate algorithm to use.",[157,1816,1818],{"id":1817},"troubleshooting","Troubleshooting",[162,1820,1821,1827,1835,1841,1847],{},[165,1822,1823,1826],{},[168,1824,1825],{},"Tiles are blank."," No layers are visible, or the extent lies outside Web Mercator's latitude range.",[165,1828,1829,1110,1832,1834],{},[168,1830,1831],{},"Labels are cut at edges.",[18,1833,1234],{}," is 1; raise it to 4 or 8.",[165,1836,1837,1840],{},[168,1838,1839],{},"Lines look thinner than on the canvas."," Symbol widths are in map units that scale differently in EPSG:3857; use millimetres or points.",[165,1842,1843,1846],{},[168,1844,1845],{},"The job runs for hours."," The zoom range is too deep; estimate first and stop a level earlier.",[165,1848,1849,1110,1852,1854],{},[168,1850,1851],{},"Tiles appear upside down in a viewer.",[18,1853,1132],{}," does not match what the viewer expects.",[157,1856,1858],{"id":1857},"conclusion","Conclusion",[14,1860,1861],{},"Estimate tile counts and render time before starting, render with a metatile size of 4 or more so labels survive tile edges, and control content with a map theme applied and restored around the render. Write MBTiles when the output must travel as one file and a directory when a web server will serve it, then load the result back and inspect it at the deepest zoom before anyone else sees it.",[157,1863,1865],{"id":1864},"frequently-asked-questions","Frequently Asked Questions",[14,1867,1868,1871],{},[168,1869,1870],{},"Should I use raster tiles or vector tiles?","\nRaster tiles reproduce QGIS cartography exactly and work everywhere. Vector tiles are smaller and restylable on the client, but the style must be rebuilt in the client's format.",[14,1873,1874,1877,1878,1793,1880,1882,1883,1886],{},[168,1875,1876],{},"How do I make retina tiles?","\nSet ",[18,1879,1799],{},[18,1881,1802],{}," to 512 and ",[18,1884,1885],{},"DPI"," to 192, then tell the web map the tiles are high resolution.",[14,1888,1889,1892],{},[168,1890,1891],{},"Can I update only part of an existing tile set?","\nRender a smaller extent into the same directory; tiles are overwritten individually. MBTiles files can be updated the same way.",[14,1894,1895,1898,1899,1902],{},[168,1896,1897],{},"Can this run on a server without a display?","\nYes. Load the project headless and call the algorithms, or use ",[18,1900,1901],{},"qgis_process run native:tilesxyzmbtiles",".",[157,1904,1906],{"id":1905},"related","Related",[162,1908,1909,1914,1920,1926,1931],{},[165,1910,1911,1913],{},[26,1912,29],{"href":28}," — the guide this recipe belongs to",[165,1915,1916],{},[26,1917,1919],{"href":1918},"\u002Fpyqgis-cartography-visualization\u002Fmap-canvas-and-image-export\u002Frender-layer-to-image-without-gui-pyqgis\u002F","Render a Layer to an Image Headless in PyQGIS",[165,1921,1922],{},[26,1923,1925],{"href":1924},"\u002Fpyqgis-cartography-visualization\u002Fmap-canvas-and-image-export\u002Fexport-map-canvas-to-image-pyqgis\u002F","Export the Map Canvas to a PNG with PyQGIS",[165,1927,1928],{},[26,1929,1930],{"href":1611},"Create a Map Theme in PyQGIS",[165,1932,1933],{},[26,1934,1936],{"href":1935},"\u002Fpyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002Fuse-qgis-process-command-line-runner\u002F","Use the qgis_process Command-Line Runner",[1938,1939,1940],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .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 .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":191,"searchDepth":209,"depth":209,"links":1942},[1943,1944,1945,1946,1947,1948,1949,1950,1951,1952,1953],{"id":159,"depth":209,"text":160},{"id":180,"depth":209,"text":181},{"id":791,"depth":209,"text":792},{"id":1227,"depth":209,"text":1228},{"id":1255,"depth":209,"text":1256},{"id":1624,"depth":209,"text":1625},{"id":1787,"depth":209,"text":1788},{"id":1817,"depth":209,"text":1818},{"id":1857,"depth":209,"text":1858},{"id":1864,"depth":209,"text":1865},{"id":1905,"depth":209,"text":1906},"Render a styled QGIS map into a raster tile pyramid with native:tilesxyzdirectory and native:tilesxyzmbtiles — choosing zoom levels and extent, estimating tile counts, controlling layers with a map theme, using metatiles for clean labels, and serving or loading the result.","md",{"slug":1957,"type":1958,"breadcrumb":1959,"datePublished":1960,"dateModified":1960},"generate-xyz-tiles-and-mbtiles-pyqgis","article","Generate XYZ Tiles and MBTiles","2026-09-17","\u002Fpyqgis-cartography-visualization\u002Fmap-canvas-and-image-export\u002Fgenerate-xyz-tiles-and-mbtiles-pyqgis",{"title":5,"description":1954},"pyqgis-cartography-visualization\u002Fmap-canvas-and-image-export\u002Fgenerate-xyz-tiles-and-mbtiles-pyqgis\u002Findex","1kPtgimVOG5jkd6rjVcc2E6nPeZYjjz7oiR0im-cIYM",1789632906584]