"""Add XYZ basemaps in QGIS with PyQGIS and render them to PNG. Run headless with the QGIS Python launcher, for example on Windows: "C:\\Program Files\\QGIS 3.40.12\\bin\\python-qgis-ltr.bat" pyqgis_xyz_basemaps.py Tested with QGIS 3.40.12-Bratislava. """ from pathlib import Path from qgis.core import ( Qgis, QgsApplication, QgsCoordinateReferenceSystem, QgsCoordinateTransform, QgsDataSourceUri, QgsMapRendererParallelJob, QgsMapSettings, QgsProject, QgsRasterLayer, QgsRectangle, ) from qgis.PyQt.QtCore import QSize from qgis.PyQt.QtGui import QColor OUT = Path(__file__).parent / "output" OUT.mkdir(exist_ok=True) BASEMAPS = { "OpenStreetMap": ("https://tile.openstreetmap.org/{z}/{x}/{y}.png", 19), "Sentinel-2 cloudless 2016 (EOX)": ( "https://tiles.maps.eox.at/wmts/1.0.0/s2cloudless_3857/default/g/{z}/{y}/{x}.jpg", 14, ), } def xyz_layer(name: str, url: str, zmax: int = 19) -> QgsRasterLayer: uri = QgsDataSourceUri() uri.setParam("type", "xyz") uri.setParam("url", url) uri.setParam("zmin", "0") uri.setParam("zmax", str(zmax)) source = bytes(uri.encodedUri()).decode() layer = QgsRasterLayer(source, name, "wms") print(f"{name}: valid={layer.isValid()} crs={layer.crs().authid()}") print(f" source={source}") return layer def main() -> None: qgs = QgsApplication([], False) qgs.initQgis() print("QGIS", Qgis.version()) project = QgsProject.instance() layers = [xyz_layer(n, u, z) for n, (u, z) in BASEMAPS.items()] project.addMapLayers(layers) wgs84 = QgsCoordinateReferenceSystem("EPSG:4326") web_merc = QgsCoordinateReferenceSystem("EPSG:3857") to_merc = QgsCoordinateTransform(wgs84, web_merc, project) # Holderness coast around Withernsea, East Yorkshire extent = to_merc.transformBoundingBox(QgsRectangle(-0.10, 53.70, 0.12, 53.80)) for layer, fname in zip(layers, ["qgis_osm.png", "qgis_eox_2016.png"]): settings = QgsMapSettings() settings.setLayers([layer]) settings.setDestinationCrs(web_merc) settings.setExtent(extent) settings.setOutputSize(QSize(1000, 700)) settings.setBackgroundColor(QColor("white")) job = QgsMapRendererParallelJob(settings) job.start() job.waitForFinished() job.renderedImage().save(str(OUT / fname)) print(f"saved {fname}; render errors: {len(job.errors())}") qgs.exitQgis() if __name__ == "__main__": main()