PyQGIS – Troubleshooting QgsRasterLayer Invalid When Run as Standalone

layerspyqgisstandalonevalidation

The following code behaves as I expect when I run it in QGIS Python Console, but when I run it as a standalone from the console, the layer is invalid.

urlWithParams = 'type=xyz&url=https://tile.openstreetmap.org/%7Bz%7D/%7Bx%7D/%7By%7D.png&zmax=19&zmin=0'
raster_layer = QgsRasterLayer(urlWithParams, 'OpenStreetMap', 'wms')
l = raster_layer

                        #==stand alone==#==QGIS Console===
print(l.isValid())      # False         # True
print(l.dataProvider()) # None          # QgsRasterDataProvider<>
print(l.providerType()) # "wms"         # "wms"
print(l.rasterType())   # 0             # 3

What do I need to set up for stand alone script to be successful with adding Open Street Maps to my project.

Complete code below

from qgis.PyQt.QtCore import Qt, QSize
from qgis.core import QgsApplication, QgsProject, \
    QgsRasterLayer, QgsCoordinateReferenceSystem, \
    QgsRectangle, QgsMapRendererParallelJob
from qgis.gui import QgsMapCanvas, QgsLayerTreeMapCanvasBridge

crs = QgsCoordinateReferenceSystem("EPSG:3857")

#### APPLICATION ####
QgsApplication.setPrefixPath("/usr/bin/qgis", True)
qgs = QgsApplication([], False)
qgs.initQgis()

#### PROJECT ####
proj = QgsProject.instance()
proj.setCrs(crs)
root = proj.layerTreeRoot()

#### CANVAS ####
canvas = QgsMapCanvas()
bridge = QgsLayerTreeMapCanvasBridge(proj.layerTreeRoot(), canvas)
canvas.setCanvasColor(Qt.red)
canvas.setDestinationCrs(crs)
extent = QgsRectangle(1042595, 7522132,1131975, 7536048)
canvas.setExtent(extent)
canvas.refresh()
canvas.update()
mapSettings = canvas.mapSettings()
mapSettings.setOutputSize( QSize(800,800) )

#### LAYERS ####
urlWithParams = 'type=xyz&url=https://tile.openstreetmap.org/%7Bz%7D/%7Bx%7D/%7By%7D.png&zmax=19&zmin=0'
raster_layer = QgsRasterLayer(urlWithParams, 'OpenStreetMap', 'wms')
proj.addMapLayer(raster_layer)
assert raster_layer.isValid()
canvas.setLayers([raster_layer])

#### RENDERING ####
job = QgsMapRendererParallelJob(mapSettings)
job.start()
job.waitForFinished()
image = job.renderedImage()
image.save('foo.png')
proj.write('foo.qgz')

Environment

The Python path looks slightly different for the two cases, but I believe they are essentially running the same environment. The following code produced the exact same output.

import sys, qgis, qgis.core
print(sys.version) # 3.8.10 (default, Mar 15 2022, 12:22:08) [GCC 9.4.0]
print(qgis.__file__) # /usr/lib/python3/dist-packages/qgis/__init__.py
print(qgis.core.Qgis.QGIS_VERSION) # 3.24.3-Tisler

Best Answer

After learning how (source), I set my code to print the error message associated with the layer being invalid:

if not raster_layer.isValid():
    print("error summary:", raster_layer.error().summary())
assert raster_layer.isValid()

And the resulting error summary was:

"Cannot instantiate the 'wms' data provider"

So I googled for this problem and found this Q&A (Can't get dataProvider object outside of QGIS python Interpreter).

In the end I simply had to use a different prefix path. And after that the same data providers were available in the stand alone as within QGIS, and now it the layer was valid.

QgsApplication.setPrefixPath("/usr", True) # instead of /usr/bin/qgis

More details

I set my code to also print the available data providers:

if not raster_layer.isValid():
    print("error summary:", raster_layer.error().summary())
from qgis.core import QgsProviderRegistry
print("providers: ", QgsProviderRegistry.instance().providerList())
assert raster_layer.isValid()

The results, when prefix path was "/usr/bin/python" were:

  • QGIS Python Console: ['DB2', 'OAPIF', 'WFS', 'arcgisfeatureserver', 'arcgismapserver', 'delimitedtext', 'ept', 'gdal', 'geonode', 'gpx', 'grass', 'grassraster', 'hana', 'mdal', 'memory', 'mesh_memory', 'mssql', 'ogr', 'pdal', 'postgres', 'postgresraster', 'spatialite', 'vectortile', 'virtual', 'virtualraster', 'wcs', 'wms']
  • standalone script / shell: ['ept', 'gdal', 'memory', 'mesh_memory', 'ogr', 'vectortile']