[{"data":1,"prerenderedAt":1417},["ShallowReactive",2],{"doc:\u002Fspatial-data-processing-automation\u002Fweb-services-and-remote-data\u002Fload-wms-layer-pyqgis":3},{"id":4,"title":5,"body":6,"description":1406,"extension":1407,"meta":1408,"navigation":235,"path":1413,"seo":1414,"stem":1415,"__hash__":1416},"docs\u002Fspatial-data-processing-automation\u002Fweb-services-and-remote-data\u002Fload-wms-layer-pyqgis\u002Findex.md","Load a WMS Layer in PyQGIS",{"type":7,"value":8,"toc":1393},"minimark",[9,13,26,35,174,179,196,200,203,303,317,321,520,564,567,621,628,632,635,687,710,804,808,811,898,918,1018,1022,1025,1096,1110,1113,1141,1149,1153,1221,1231,1235,1288,1292,1305,1309,1315,1328,1337,1346,1354,1358,1389],[10,11,5],"h1",{"id":12},"load-a-wms-layer-in-pyqgis",[14,15,16,17,21,22,25],"p",{},"Adding a WMS layer through the QGIS interface is four clicks. Doing it from Python is one line — provided the URI is right, and getting it right is the entire task. The provider gives no encouragement when it is wrong: you get a layer object, ",[18,19,20],"code",{},"isValid()"," returns ",[18,23,24],{},"False",", and the error message is often a bare HTTP status. This recipe removes the guesswork by building the URI with the API instead of by hand, and by asking the service what it actually offers before requesting anything.",[14,27,28,29,34],{},"It belongs to ",[30,31,33],"a",{"href":32},"\u002Fspatial-data-processing-automation\u002Fweb-services-and-remote-data\u002F","Web Services and Remote Data in PyQGIS",", which covers the wider question of when a rendered image service is the right choice at all.",[14,36,37],{},[38,39,44,48,52,59,76,85,93,99,104,108,113,117,122,126,130,134,138,144,149,152,156,158,162,166,170],"svg",{"viewBox":40,"role":41,"ariaLabel":42,"xmlns":43},"0 0 760 258","img","Sequence of a WMS layer load showing the capabilities request, the parsed layer list and coordinate systems, the GetMap request for the current view, and the rendered image returned as a raster layer","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[45,46,47],"title",{},"What QGIS does when you add a WMS layer",[49,50,51],"desc",{},"QGIS first requests the capabilities document, which lists the published layers, the coordinate systems each supports and the image formats available. It then issues a GetMap request for the current canvas extent and size, and the server returns a rendered image which QGIS displays as a raster layer. Every pan or zoom issues a new GetMap request.",[53,54],"rect",{"x":55,"y":55,"width":56,"height":57,"fill":58},"0","760","258","#f6f3ea",[60,61,62],"defs",{},[63,64,71],"marker",{"id":65,"viewBox":66,"refX":67,"refY":68,"markerWidth":69,"markerHeight":69,"orient":70},"wmsSeqArrow","0 0 10 10","8","5","7","auto-start-reverse",[72,73],"path",{"d":74,"fill":75},"M0 0 L10 5 L0 10 z","#2f3b35",[77,78,84],"text",{"x":79,"y":80,"style":81,"fill":82,"textAnchor":83},"380","28","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","Two requests: one to find out, one to draw",[53,86],{"x":87,"y":88,"width":89,"height":89,"rx":67,"fill":90,"stroke":91,"style":92},"24","56","150","#eef7f4","#0f766e","stroke-width:2.5",[77,94,98],{"x":95,"y":96,"style":97,"fill":91,"textAnchor":83},"99","82","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","your script",[77,100,103],{"x":95,"y":101,"style":102,"fill":75,"textAnchor":83},"108","text-anchor:middle;font-size:11px;font-family:sans-serif","QgsRasterLayer",[77,105,107],{"x":95,"y":106,"style":102,"fill":75,"textAnchor":83},"128","provider: wms",[77,109,112],{"x":95,"y":110,"style":102,"fill":111,"textAnchor":83},"156","#59645f","checks isValid()",[77,114,116],{"x":95,"y":115,"style":102,"fill":111,"textAnchor":83},"176","before adding",[53,118],{"x":119,"y":88,"width":89,"height":89,"rx":67,"fill":120,"stroke":121,"style":92},"586","#eff3ff","#2563eb",[77,123,125],{"x":124,"y":96,"style":97,"fill":121,"textAnchor":83},"661","WMS server",[77,127,129],{"x":124,"y":128,"style":102,"fill":75,"textAnchor":83},"112","holds the data",[77,131,133],{"x":124,"y":132,"style":102,"fill":75,"textAnchor":83},"132","and the styling",[77,135,137],{"x":124,"y":136,"style":102,"fill":111,"textAnchor":83},"160","renders per request",[139,140],"line",{"x1":115,"y1":141,"x2":142,"y2":141,"stroke":75,"style":143},"92","580","stroke-width:2;marker-end:url(#wmsSeqArrow)",[77,145,148],{"x":146,"y":147,"style":102,"fill":75,"textAnchor":83},"378","84","GetCapabilities",[139,150],{"x1":142,"y1":151,"x2":115,"y2":151,"stroke":91,"style":143},"122",[77,153,155],{"x":146,"y":154,"style":102,"fill":91,"textAnchor":83},"114","layer names, CRS list, formats",[139,157],{"x1":115,"y1":136,"x2":142,"y2":136,"stroke":75,"style":143},[77,159,161],{"x":146,"y":160,"style":102,"fill":75,"textAnchor":83},"152","GetMap — bbox, width, height, crs",[139,163],{"x1":142,"y1":164,"x2":115,"y2":164,"stroke":165,"style":143},"190","#15803d",[77,167,169],{"x":146,"y":168,"style":102,"fill":165,"textAnchor":83},"182","a PNG of that extent",[77,171,173],{"x":79,"y":172,"style":102,"fill":111,"textAnchor":83},"234","Every pan and zoom repeats the second exchange",[175,176,178],"h2",{"id":177},"prerequisites","Prerequisites",[180,181,182,190,193],"ul",{},[183,184,185,189],"li",{},[186,187,188],"strong",{},"QGIS 3.34 LTR"," (bundled Python 3.12) or newer.",[183,191,192],{},"The base URL of a WMS service — the part before the question mark.",[183,194,195],{},"Network access from the machine running the script, including through any proxy QGIS is configured to use.",[175,197,199],{"id":198},"discover-what-the-service-publishes","Discover what the service publishes",[14,201,202],{},"Before hard-coding a layer name, ask the server. The provider metadata API parses the capabilities document for you.",[204,205,210],"pre",{"className":206,"code":207,"language":208,"meta":209,"style":209},"language-python shiki shiki-themes github-dark","from qgis.core import QgsProviderRegistry\n\nmetadata = QgsProviderRegistry.instance().providerMetadata(\"wms\")\nconnection = metadata.createConnection(\"https:\u002F\u002Fmaps.example.org\u002Fgeoserver\u002Fwms\", {})\n\nfor layer in connection.tables():\n    print(layer.tableName())\n","python","",[18,211,212,230,237,256,273,278,293],{"__ignoreMap":209},[213,214,216,220,224,227],"span",{"class":139,"line":215},1,[213,217,219],{"class":218},"snl16","from",[213,221,223],{"class":222},"s95oV"," qgis.core ",[213,225,226],{"class":218},"import",[213,228,229],{"class":222}," QgsProviderRegistry\n",[213,231,233],{"class":139,"line":232},2,[213,234,236],{"emptyLinePlaceholder":235},true,"\n",[213,238,240,243,246,249,253],{"class":139,"line":239},3,[213,241,242],{"class":222},"metadata ",[213,244,245],{"class":218},"=",[213,247,248],{"class":222}," QgsProviderRegistry.instance().providerMetadata(",[213,250,252],{"class":251},"sU2Wk","\"wms\"",[213,254,255],{"class":222},")\n",[213,257,259,262,264,267,270],{"class":139,"line":258},4,[213,260,261],{"class":222},"connection ",[213,263,245],{"class":218},[213,265,266],{"class":222}," metadata.createConnection(",[213,268,269],{"class":251},"\"https:\u002F\u002Fmaps.example.org\u002Fgeoserver\u002Fwms\"",[213,271,272],{"class":222},", {})\n",[213,274,276],{"class":139,"line":275},5,[213,277,236],{"emptyLinePlaceholder":235},[213,279,281,284,287,290],{"class":139,"line":280},6,[213,282,283],{"class":218},"for",[213,285,286],{"class":222}," layer ",[213,288,289],{"class":218},"in",[213,291,292],{"class":222}," connection.tables():\n",[213,294,296,300],{"class":139,"line":295},7,[213,297,299],{"class":298},"sDLfK","    print",[213,301,302],{"class":222},"(layer.tableName())\n",[14,304,305,308,309,312,313,316],{},[186,306,307],{},"Breakdown:"," ",[18,310,311],{},"createConnection()"," with a URL and an empty options dictionary builds a throwaway connection rather than saving one into the user's settings — appropriate in a script that only wants to look. ",[18,314,315],{},"tables()"," returns the advertised layers using the same interface the browser panel uses, so what you see here is exactly what a user would see. If this call fails, the problem is the service or the network, and no amount of URI tuning will help; that is useful to establish before spending an hour on parameters.",[175,318,320],{"id":319},"build-the-uri-and-load-the-layer","Build the URI and load the layer",[204,322,324],{"className":206,"code":323,"language":208,"meta":209,"style":209},"from qgis.core import QgsDataSourceUri, QgsRasterLayer, QgsProject\n\nuri = QgsDataSourceUri()\nuri.setParam(\"url\", \"https:\u002F\u002Fmaps.example.org\u002Fgeoserver\u002Fwms\")\nuri.setParam(\"layers\", \"topo:contours\")\nuri.setParam(\"styles\", \"\")\nuri.setParam(\"format\", \"image\u002Fpng\")\nuri.setParam(\"crs\", \"EPSG:27700\")\n\nlayer = QgsRasterLayer(str(uri.encodedUri(), \"utf-8\"), \"Contours\", \"wms\")\n\nif not layer.isValid():\n    raise RuntimeError(f\"WMS layer invalid: {layer.error().summary()}\")\n\nQgsProject.instance().addMapLayer(layer)\n",[18,325,326,337,341,351,366,380,394,408,423,428,460,465,477,509,514],{"__ignoreMap":209},[213,327,328,330,332,334],{"class":139,"line":215},[213,329,219],{"class":218},[213,331,223],{"class":222},[213,333,226],{"class":218},[213,335,336],{"class":222}," QgsDataSourceUri, QgsRasterLayer, QgsProject\n",[213,338,339],{"class":139,"line":232},[213,340,236],{"emptyLinePlaceholder":235},[213,342,343,346,348],{"class":139,"line":239},[213,344,345],{"class":222},"uri ",[213,347,245],{"class":218},[213,349,350],{"class":222}," QgsDataSourceUri()\n",[213,352,353,356,359,362,364],{"class":139,"line":258},[213,354,355],{"class":222},"uri.setParam(",[213,357,358],{"class":251},"\"url\"",[213,360,361],{"class":222},", ",[213,363,269],{"class":251},[213,365,255],{"class":222},[213,367,368,370,373,375,378],{"class":139,"line":275},[213,369,355],{"class":222},[213,371,372],{"class":251},"\"layers\"",[213,374,361],{"class":222},[213,376,377],{"class":251},"\"topo:contours\"",[213,379,255],{"class":222},[213,381,382,384,387,389,392],{"class":139,"line":280},[213,383,355],{"class":222},[213,385,386],{"class":251},"\"styles\"",[213,388,361],{"class":222},[213,390,391],{"class":251},"\"\"",[213,393,255],{"class":222},[213,395,396,398,401,403,406],{"class":139,"line":295},[213,397,355],{"class":222},[213,399,400],{"class":251},"\"format\"",[213,402,361],{"class":222},[213,404,405],{"class":251},"\"image\u002Fpng\"",[213,407,255],{"class":222},[213,409,411,413,416,418,421],{"class":139,"line":410},8,[213,412,355],{"class":222},[213,414,415],{"class":251},"\"crs\"",[213,417,361],{"class":222},[213,419,420],{"class":251},"\"EPSG:27700\"",[213,422,255],{"class":222},[213,424,426],{"class":139,"line":425},9,[213,427,236],{"emptyLinePlaceholder":235},[213,429,431,434,436,439,442,445,448,451,454,456,458],{"class":139,"line":430},10,[213,432,433],{"class":222},"layer ",[213,435,245],{"class":218},[213,437,438],{"class":222}," QgsRasterLayer(",[213,440,441],{"class":298},"str",[213,443,444],{"class":222},"(uri.encodedUri(), ",[213,446,447],{"class":251},"\"utf-8\"",[213,449,450],{"class":222},"), ",[213,452,453],{"class":251},"\"Contours\"",[213,455,361],{"class":222},[213,457,252],{"class":251},[213,459,255],{"class":222},[213,461,463],{"class":139,"line":462},11,[213,464,236],{"emptyLinePlaceholder":235},[213,466,468,471,474],{"class":139,"line":467},12,[213,469,470],{"class":218},"if",[213,472,473],{"class":218}," not",[213,475,476],{"class":222}," layer.isValid():\n",[213,478,480,483,486,489,492,495,498,501,504,507],{"class":139,"line":479},13,[213,481,482],{"class":218},"    raise",[213,484,485],{"class":298}," RuntimeError",[213,487,488],{"class":222},"(",[213,490,491],{"class":218},"f",[213,493,494],{"class":251},"\"WMS layer invalid: ",[213,496,497],{"class":298},"{",[213,499,500],{"class":222},"layer.error().summary()",[213,502,503],{"class":298},"}",[213,505,506],{"class":251},"\"",[213,508,255],{"class":222},[213,510,512],{"class":139,"line":511},14,[213,513,236],{"emptyLinePlaceholder":235},[213,515,517],{"class":139,"line":516},15,[213,518,519],{"class":222},"QgsProject.instance().addMapLayer(layer)\n",[14,521,522,524,525,528,529,532,533,536,537,540,541,544,545,547,548,551,552,555,556,559,560,563],{},[186,523,307],{}," Five parameters are the minimum for a WMS source and each has a trap. ",[18,526,527],{},"url"," must be the endpoint without a query string — QGIS appends its own. ",[18,530,531],{},"layers"," is the machine name from the capabilities document, not the human title shown in the browser panel, and workspace-qualified names such as ",[18,534,535],{},"topo:contours"," need the colon escaped, which is precisely what ",[18,538,539],{},"setParam()"," does for you. ",[18,542,543],{},"styles"," must be present even when empty, and must have the same number of entries as ",[18,546,531],{}," when you request several. ",[18,549,550],{},"format"," has to be one the server advertises; ",[18,553,554],{},"image\u002Fpng"," supports transparency, ",[18,557,558],{},"image\u002Fjpeg"," does not. ",[18,561,562],{},"crs"," must appear in that layer's advertised list — requesting one it does not offer is the single most common cause of a valid-looking but blank layer.",[14,565,566],{},"Requesting several layers in one source stacks them server-side, which is faster than adding them separately:",[204,568,570],{"className":206,"code":569,"language":208,"meta":209,"style":209},"uri.setParam(\"layers\", \"topo:contours\")\nuri.setParam(\"layers\", \"topo:spot_heights\")\nuri.setParam(\"styles\", \"\")\nuri.setParam(\"styles\", \"\")\n",[18,571,572,584,597,609],{"__ignoreMap":209},[213,573,574,576,578,580,582],{"class":139,"line":215},[213,575,355],{"class":222},[213,577,372],{"class":251},[213,579,361],{"class":222},[213,581,377],{"class":251},[213,583,255],{"class":222},[213,585,586,588,590,592,595],{"class":139,"line":232},[213,587,355],{"class":222},[213,589,372],{"class":251},[213,591,361],{"class":222},[213,593,594],{"class":251},"\"topo:spot_heights\"",[213,596,255],{"class":222},[213,598,599,601,603,605,607],{"class":139,"line":239},[213,600,355],{"class":222},[213,602,386],{"class":251},[213,604,361],{"class":222},[213,606,391],{"class":251},[213,608,255],{"class":222},[213,610,611,613,615,617,619],{"class":139,"line":258},[213,612,355],{"class":222},[213,614,386],{"class":251},[213,616,361],{"class":222},[213,618,391],{"class":251},[213,620,255],{"class":222},[14,622,623,308,625,627],{},[186,624,307],{},[18,626,539],{}," called twice with the same key appends rather than replacing, which is how the WMS provider expresses a list. The styles list must be padded to match, empty strings included, or the server rejects the request with a mismatched-parameter error that names neither list.",[175,629,631],{"id":630},"authentication-and-extra-parameters","Authentication and extra parameters",[14,633,634],{},"Services behind a login or an API key should not carry credentials in the URI, because the URI is written into the project file.",[204,636,638],{"className":206,"code":637,"language":208,"meta":209,"style":209},"uri.setParam(\"authcfg\", \"a1b2c3d\")                 # stored configuration id\nuri.setParam(\"dpiMode\", \"7\")\nuri.setParam(\"contextualWMSLegend\", \"0\")\n",[18,639,640,659,673],{"__ignoreMap":209},[213,641,642,644,647,649,652,655],{"class":139,"line":215},[213,643,355],{"class":222},[213,645,646],{"class":251},"\"authcfg\"",[213,648,361],{"class":222},[213,650,651],{"class":251},"\"a1b2c3d\"",[213,653,654],{"class":222},")                 ",[213,656,658],{"class":657},"sjoCn","# stored configuration id\n",[213,660,661,663,666,668,671],{"class":139,"line":232},[213,662,355],{"class":222},[213,664,665],{"class":251},"\"dpiMode\"",[213,667,361],{"class":222},[213,669,670],{"class":251},"\"7\"",[213,672,255],{"class":222},[213,674,675,677,680,682,685],{"class":139,"line":239},[213,676,355],{"class":222},[213,678,679],{"class":251},"\"contextualWMSLegend\"",[213,681,361],{"class":222},[213,683,684],{"class":251},"\"0\"",[213,686,255],{"class":222},[14,688,689,308,691,694,695,699,700,703,704,706,707,709],{},[186,690,307],{},[18,692,693],{},"authcfg"," refers to an entry in QGIS's encrypted authentication database, so the project can be shared without the secret travelling with it — the same mechanism used for database connections in ",[30,696,698],{"href":697},"\u002Fspatial-data-processing-automation\u002Fpostgis-and-database-workflows\u002Fconnect-to-postgis-database-pyqgis\u002F","Connect to a PostGIS Database in PyQGIS",". ",[18,701,702],{},"dpiMode"," controls how QGIS reconciles its own resolution with the server's; ",[18,705,69],{}," (all modes) is the tolerant default and is worth setting explicitly when a layout export comes out at the wrong scale. Any parameter the service supports but QGIS does not model — a time dimension, a vendor-specific option — can be appended to the ",[18,708,527],{}," parameter as a query string, provided it is percent-encoded.",[14,711,712],{},[38,713,716,719,722,725,728,737,742,746,750,754,759,763,767,770,774,776,779,782,786,788,791,795,799,801],{"viewBox":714,"role":41,"ariaLabel":715,"xmlns":43},"0 0 760 246","Table matching four common WMS failure symptoms to their causes, covering an unsupported coordinate system, a wrong layer name, a mismatched styles list and a missing transparency parameter",[45,717,718],{},"Blank, invalid, or wrong: matching symptom to cause",[49,720,721],{},"Four rows pair a symptom with its usual cause. A valid but blank layer usually means the requested coordinate system is not supported for that layer. An invalid layer usually means the layer name came from the human readable title rather than the machine name. A server error mentioning parameters usually means the styles list length does not match the layers list. An opaque white background usually means a JPEG format was requested instead of PNG with transparency.",[53,723],{"x":55,"y":55,"width":56,"height":724,"fill":58},"246",[77,726,727],{"x":79,"y":80,"style":81,"fill":82,"textAnchor":83},"Four symptoms that all look like the service is broken",[53,729],{"x":730,"y":731,"width":732,"height":733,"rx":734,"fill":735,"stroke":111,"style":736},"16","46","332","30","6","#efeadd","stroke-width:1.5",[77,738,741],{"x":168,"y":739,"style":740,"fill":82,"textAnchor":83},"66","text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif","what you see",[53,743],{"x":744,"y":731,"width":745,"height":733,"rx":734,"fill":735,"stroke":111,"style":736},"356","388",[77,747,749],{"x":748,"y":739,"style":740,"fill":82,"textAnchor":83},"550","what it almost always is",[53,751],{"x":730,"y":96,"width":732,"height":752,"rx":734,"fill":753,"stroke":111,"style":736},"36","#fffdf7",[77,755,758],{"x":733,"y":756,"style":757,"fill":75},"105","font-size:11px;font-family:sans-serif","valid layer, nothing drawn",[53,760],{"x":744,"y":96,"width":745,"height":752,"rx":734,"fill":761,"stroke":762,"style":736},"#fdf2e2","#b45309",[77,764,766],{"x":765,"y":756,"style":757,"fill":75},"370","crs not supported for that layer",[53,768],{"x":730,"y":769,"width":732,"height":752,"rx":734,"fill":753,"stroke":111,"style":736},"124",[77,771,773],{"x":733,"y":772,"style":757,"fill":75},"147","isValid() is False",[53,775],{"x":744,"y":769,"width":745,"height":752,"rx":734,"fill":761,"stroke":762,"style":736},[77,777,778],{"x":765,"y":772,"style":757,"fill":75},"layer title used instead of the machine name",[53,780],{"x":730,"y":781,"width":732,"height":752,"rx":734,"fill":753,"stroke":111,"style":736},"166",[77,783,785],{"x":733,"y":784,"style":757,"fill":75},"189","server complains about parameters",[53,787],{"x":744,"y":781,"width":745,"height":752,"rx":734,"fill":761,"stroke":762,"style":736},[77,789,790],{"x":765,"y":784,"style":757,"fill":75},"styles list shorter than the layers list",[53,792],{"x":730,"y":793,"width":732,"height":794,"rx":734,"fill":753,"stroke":111,"style":736},"208","34",[77,796,798],{"x":733,"y":797,"style":757,"fill":75},"230","white box hides everything below",[53,800],{"x":744,"y":793,"width":745,"height":794,"rx":734,"fill":761,"stroke":762,"style":736},[77,802,803],{"x":765,"y":797,"style":757,"fill":75},"jpeg requested — png carries transparency",[175,805,807],{"id":806},"query-a-wms-layer-with-getfeatureinfo","Query a WMS layer with GetFeatureInfo",[14,809,810],{},"A WMS layer has no attribute table, but many services answer a point query. The provider exposes it through the raster identify API.",[204,812,814],{"className":206,"code":813,"language":208,"meta":209,"style":209},"from qgis.core import QgsRaster, QgsPointXY\n\nresult = layer.dataProvider().identify(\n    QgsPointXY(432100, 187650),\n    QgsRaster.IdentifyFormatHtml,\n)\n\nif result.isValid():\n    for key, value in result.results().items():\n        print(key, value)\n",[18,815,816,827,831,841,857,862,866,870,877,890],{"__ignoreMap":209},[213,817,818,820,822,824],{"class":139,"line":215},[213,819,219],{"class":218},[213,821,223],{"class":222},[213,823,226],{"class":218},[213,825,826],{"class":222}," QgsRaster, QgsPointXY\n",[213,828,829],{"class":139,"line":232},[213,830,236],{"emptyLinePlaceholder":235},[213,832,833,836,838],{"class":139,"line":239},[213,834,835],{"class":222},"result ",[213,837,245],{"class":218},[213,839,840],{"class":222}," layer.dataProvider().identify(\n",[213,842,843,846,849,851,854],{"class":139,"line":258},[213,844,845],{"class":222},"    QgsPointXY(",[213,847,848],{"class":298},"432100",[213,850,361],{"class":222},[213,852,853],{"class":298},"187650",[213,855,856],{"class":222},"),\n",[213,858,859],{"class":139,"line":275},[213,860,861],{"class":222},"    QgsRaster.IdentifyFormatHtml,\n",[213,863,864],{"class":139,"line":280},[213,865,255],{"class":222},[213,867,868],{"class":139,"line":295},[213,869,236],{"emptyLinePlaceholder":235},[213,871,872,874],{"class":139,"line":410},[213,873,470],{"class":218},[213,875,876],{"class":222}," result.isValid():\n",[213,878,879,882,885,887],{"class":139,"line":425},[213,880,881],{"class":218},"    for",[213,883,884],{"class":222}," key, value ",[213,886,289],{"class":218},[213,888,889],{"class":222}," result.results().items():\n",[213,891,892,895],{"class":139,"line":430},[213,893,894],{"class":298},"        print",[213,896,897],{"class":222},"(key, value)\n",[14,899,900,308,902,905,906,909,910,912,913,917],{},[186,901,307],{},[18,903,904],{},"identify()"," issues a GetFeatureInfo request at that map coordinate, in the layer's coordinate system, and returns whatever the server chooses to send — HTML for display, or ",[18,907,908],{},"IdentifyFormatFeature"," where the service returns structured features QGIS can turn into real feature objects. Support is optional and many services decline, so ",[18,911,20],{}," on the result is doing real work here. This is also the mechanism behind the identify tool in the interface, which makes it a good way to test a custom map tool of the kind built in ",[30,914,916],{"href":915},"\u002Fqgis-plugin-development\u002Fcustom-map-tools-and-canvas-interaction\u002Fcreate-custom-map-tool-pyqgis\u002F","Create a Custom Map Tool in PyQGIS",".",[14,919,920],{},[38,921,924,927,930,933,940,943,949,952,955,961,965,969,972,976,979,982,986,989,992,996,999,1006,1010,1014],{"viewBox":922,"role":41,"ariaLabel":923,"xmlns":43},"0 0 760 244","Diagram of the capabilities document as the source of truth for layer names, supported coordinate systems and image formats",[45,925,926],{},"Everything you need is in the capabilities document",[49,928,929],{},"The capabilities document lists each published layer with its machine name, the coordinate systems it supports, the image formats available and its declared extent. Reading it first replaces guessing, and three of the four values it carries are the ones most often wrong in a hand-written URI.",[53,931],{"x":55,"y":55,"width":56,"height":932,"fill":58},"244",[60,934,935],{},[63,936,938],{"id":937,"viewBox":66,"refX":67,"refY":68,"markerWidth":69,"markerHeight":69,"orient":70},"capArrow",[72,939],{"d":74,"fill":91},[77,941,942],{"x":79,"y":80,"style":81,"fill":82,"textAnchor":83},"Ask the server instead of guessing",[53,944],{"x":945,"y":946,"width":947,"height":88,"rx":948,"fill":90,"stroke":91,"style":92},"272","48","216","10",[77,950,148],{"x":79,"y":951,"style":97,"fill":91,"textAnchor":83},"72",[77,953,954],{"x":79,"y":141,"style":102,"fill":75,"textAnchor":83},"parsed for you by the provider",[53,956],{"x":957,"y":958,"width":959,"height":951,"rx":67,"fill":753,"stroke":121,"style":960},"14","148","172","stroke-width:2",[77,962,964],{"x":963,"y":115,"style":740,"fill":121,"textAnchor":83},"100","machine names",[77,966,968],{"x":963,"y":967,"style":102,"fill":75,"textAnchor":83},"198","not the titles shown",[53,970],{"x":971,"y":958,"width":959,"height":951,"rx":67,"fill":753,"stroke":762,"style":92},"200",[77,973,975],{"x":974,"y":115,"style":740,"fill":762,"textAnchor":83},"286","supported CRS list",[77,977,978],{"x":974,"y":967,"style":102,"fill":75,"textAnchor":83},"the blank-layer cause",[53,980],{"x":981,"y":958,"width":959,"height":951,"rx":67,"fill":753,"stroke":111,"style":960},"386",[77,983,985],{"x":984,"y":115,"style":740,"fill":82,"textAnchor":83},"472","image formats",[77,987,988],{"x":984,"y":967,"style":102,"fill":75,"textAnchor":83},"png for transparency",[53,990],{"x":991,"y":958,"width":959,"height":951,"rx":67,"fill":753,"stroke":111,"style":960},"572",[77,993,995],{"x":994,"y":115,"style":740,"fill":82,"textAnchor":83},"658","declared extent",[77,997,998],{"x":994,"y":967,"style":102,"fill":75,"textAnchor":83},"where to zoom first",[139,1000],{"x1":1001,"y1":1002,"x2":1003,"y2":1004,"stroke":91,"style":1005},"330","104","120","142","stroke-width:2;marker-end:url(#capArrow)",[139,1007],{"x1":1008,"y1":1002,"x2":1009,"y2":1004,"stroke":91,"style":1005},"360","290",[139,1011],{"x1":1012,"y1":1002,"x2":1013,"y2":1004,"stroke":91,"style":1005},"400","468",[139,1015],{"x1":1016,"y1":1002,"x2":1017,"y2":1004,"stroke":91,"style":1005},"430","640",[175,1019,1021],{"id":1020},"control-transparency-scale-limits-and-refresh","Control transparency, scale limits and refresh",[14,1023,1024],{},"Once the layer loads, a few properties turn it from a technically-present base layer into one that behaves well in a real map.",[204,1026,1028],{"className":206,"code":1027,"language":208,"meta":209,"style":209},"basemap = layer                                   # the WMS layer loaded above\nbasemap.renderer().setOpacity(0.65)\nbasemap.setScaleBasedVisibility(True)\nbasemap.setMinimumScale(250000)                   # hidden when zoomed out beyond this\nbasemap.setMaximumScale(1000)                     # hidden when zoomed in beyond this\nbasemap.triggerRepaint()\n",[18,1029,1030,1043,1053,1063,1077,1091],{"__ignoreMap":209},[213,1031,1032,1035,1037,1040],{"class":139,"line":215},[213,1033,1034],{"class":222},"basemap ",[213,1036,245],{"class":218},[213,1038,1039],{"class":222}," layer                                   ",[213,1041,1042],{"class":657},"# the WMS layer loaded above\n",[213,1044,1045,1048,1051],{"class":139,"line":232},[213,1046,1047],{"class":222},"basemap.renderer().setOpacity(",[213,1049,1050],{"class":298},"0.65",[213,1052,255],{"class":222},[213,1054,1055,1058,1061],{"class":139,"line":239},[213,1056,1057],{"class":222},"basemap.setScaleBasedVisibility(",[213,1059,1060],{"class":298},"True",[213,1062,255],{"class":222},[213,1064,1065,1068,1071,1074],{"class":139,"line":258},[213,1066,1067],{"class":222},"basemap.setMinimumScale(",[213,1069,1070],{"class":298},"250000",[213,1072,1073],{"class":222},")                   ",[213,1075,1076],{"class":657},"# hidden when zoomed out beyond this\n",[213,1078,1079,1082,1085,1088],{"class":139,"line":275},[213,1080,1081],{"class":222},"basemap.setMaximumScale(",[213,1083,1084],{"class":298},"1000",[213,1086,1087],{"class":222},")                     ",[213,1089,1090],{"class":657},"# hidden when zoomed in beyond this\n",[213,1092,1093],{"class":139,"line":280},[213,1094,1095],{"class":222},"basemap.triggerRepaint()\n",[14,1097,1098,1100,1101,1105,1106,1109],{},[186,1099,307],{}," Opacity on the renderer rather than on individual pixels is the right lever for a background image, and 60 to 70 percent is the usual range that keeps an aerial photograph readable underneath vector work. Scale-based visibility is what stops a detailed service being requested at continental zoom, which is both slow and useless — note that in QGIS the ",[1102,1103,1104],"em",{},"minimum"," scale is the zoomed-out limit, which reads backwards until you remember that scale denominators get larger as you zoom out. ",[18,1107,1108],{},"triggerRepaint()"," applies the change immediately rather than waiting for the next pan.",[14,1111,1112],{},"Services whose content changes — a live radar composite, a daily satellite mosaic — need one more setting, or the user sees a cached image indefinitely:",[204,1114,1116],{"className":206,"code":1115,"language":208,"meta":209,"style":209},"basemap.setAutoRefreshInterval(300000)            # milliseconds\nbasemap.setAutoRefreshEnabled(True)\n",[18,1117,1118,1132],{"__ignoreMap":209},[213,1119,1120,1123,1126,1129],{"class":139,"line":215},[213,1121,1122],{"class":222},"basemap.setAutoRefreshInterval(",[213,1124,1125],{"class":298},"300000",[213,1127,1128],{"class":222},")            ",[213,1130,1131],{"class":657},"# milliseconds\n",[213,1133,1134,1137,1139],{"class":139,"line":232},[213,1135,1136],{"class":222},"basemap.setAutoRefreshEnabled(",[213,1138,1060],{"class":298},[213,1140,255],{"class":222},[14,1142,1143,1145,1146,1148],{},[186,1144,307],{}," Auto-refresh re-requests the layer on a timer, which is exactly right for an operational display and exactly wrong for a scheduled export, where it wastes requests on a map nobody is watching. Five minutes is a sensible floor for a public service; anything shorter should be against infrastructure you control. For a genuinely time-aware service, the WMS time dimension is passed as an extra query parameter on the ",[18,1147,527],{}," value, which lets you request a specific timestamp rather than whatever the server considers current.",[175,1150,1152],{"id":1151},"qgis-version-compatibility","QGIS version compatibility",[1154,1155,1156,1172],"table",{},[1157,1158,1159],"thead",{},[1160,1161,1162,1166,1169],"tr",{},[1163,1164,1165],"th",{},"QGIS version",[1163,1167,1168],{},"Python",[1163,1170,1171],{},"Notes",[1173,1174,1175,1190,1200,1211],"tbody",{},[1160,1176,1177,1181,1184],{},[1178,1179,1180],"td",{},"3.22 LTR",[1178,1182,1183],{},"3.9",[1178,1185,1186,1187,1189],{},"WMS 1.1.1 and 1.3.0, ",[18,1188,693],{},", and identify as described.",[1160,1191,1192,1195,1197],{},[1178,1193,1194],{},"3.28 LTR",[1178,1196,1183],{},[1178,1198,1199],{},"Identical.",[1160,1201,1202,1205,1208],{},[1178,1203,1204],{},"3.34 LTR",[1178,1206,1207],{},"3.12",[1178,1209,1210],{},"Baseline for this page; provider connection API available for discovery.",[1160,1212,1213,1216,1218],{},[1178,1214,1215],{},"3.40 \u002F 3.44",[1178,1217,1207],{},[1178,1219,1220],{},"Identical loading behaviour; the browser panel gained better capabilities caching, which does not change these calls.",[14,1222,1223,1224,1227,1228,1230],{},"WMS version is negotiated by the provider. Where a server misbehaves on 1.3.0 — the axis-order change between versions is a classic source of maps that appear rotated or in the sea — pin it by adding ",[18,1225,1226],{},"version=1.1.1"," to the ",[18,1229,527],{}," parameter's query string.",[175,1232,1234],{"id":1233},"troubleshooting","Troubleshooting",[180,1236,1237,1248,1257,1267,1273,1282],{},[183,1238,1239,1247],{},[186,1240,1241,1243,1244,1246],{},[18,1242,20],{}," is ",[18,1245,24],{}," with an HTTP 404."," The endpoint URL is wrong, or a query string was left on it. Strip everything from the question mark onwards.",[183,1249,1250,1253,1254,1256],{},[186,1251,1252],{},"HTTP 401 or 403."," The service needs credentials. Create an authentication configuration and pass ",[18,1255,693],{},"; check whether the key must be a header rather than a parameter.",[183,1258,1259,1262,1263,1266],{},[186,1260,1261],{},"The layer is valid but the canvas is empty."," Either the requested CRS is not supported for that layer, or the canvas is outside the layer's advertised extent. Zoom to ",[18,1264,1265],{},"layer.extent()"," first.",[183,1268,1269,1272],{},[186,1270,1271],{},"The image is rotated or in the wrong hemisphere."," Axis-order confusion between WMS 1.1.1 and 1.3.0. Pin the version in the URL.",[183,1274,1275,1278,1279,1281],{},[186,1276,1277],{},"Everything below the layer is hidden."," JPEG has no alpha channel. Request ",[18,1280,554],{},", and set the layer's own transparency if the server bakes in a background.",[183,1283,1284,1287],{},[186,1285,1286],{},"It works interactively but not in a scheduled job."," The job runs as a different user, without the proxy settings or the authentication database the interactive session has.",[175,1289,1291],{"id":1290},"conclusion","Conclusion",[14,1293,1294,1295,1298,1299,1301,1302,1304],{},"Build the URI with ",[18,1296,1297],{},"QgsDataSourceUri"," and ",[18,1300,539],{}," so escaping is handled for you, take the layer name from the capabilities document rather than the title, request a CRS the layer actually supports, and check ",[18,1303,20],{}," before adding the layer to the project. When something goes wrong, read the provider's error text first — it distinguishes an authentication problem from a naming problem from an unsupported projection, which are three very different afternoons.",[175,1306,1308],{"id":1307},"frequently-asked-questions","Frequently Asked Questions",[14,1310,1311,1314],{},[186,1312,1313],{},"Can I run analysis on a WMS layer?","\nNo, in any meaningful sense: it delivers a picture, not values. Look for a WCS or WFS endpoint on the same service, or a downloadable dataset, when you need the numbers behind the image.",[14,1316,1317,1320,1321,1324,1325,1327],{},[186,1318,1319],{},"How do I add several WMS layers from one service?","\nEither add them as separate layers, or request them in one source by calling ",[18,1322,1323],{},"setParam(\"layers\", ...)"," repeatedly with a matching number of ",[18,1326,543],{}," entries. One source is faster; separate layers give the user individual visibility control.",[14,1329,1330,1333,1334,1336],{},[186,1331,1332],{},"Why does the WMS look worse in a layout than on screen?","\nThe export requests a much larger image, and many servers cap the pixel dimensions of a single GetMap response. Set ",[18,1335,702],{}," explicitly, and consider tiling the export or using a vector source for print.",[14,1338,1339,1342,1343,1345],{},[186,1340,1341],{},"Where do I put an API key?","\nIn a QGIS authentication configuration referenced by ",[18,1344,693],{},". Keys pasted into the URI are saved into the project file and travel with it.",[14,1347,1348,1351,1352,917],{},[186,1349,1350],{},"Does a WMS layer work offline?","\nOnly from cache, and not reliably. Cache what you need locally before going into the field — the caching pattern is in ",[30,1353,33],{"href":32},[175,1355,1357],{"id":1356},"related","Related",[180,1359,1360,1365,1371,1377,1383],{},[183,1361,1362,1364],{},[30,1363,33],{"href":32}," — the guide this recipe belongs to",[183,1366,1367],{},[30,1368,1370],{"href":1369},"\u002Fspatial-data-processing-automation\u002Fweb-services-and-remote-data\u002Fadd-xyz-tile-basemap-pyqgis\u002F","Add an XYZ Tile Basemap in PyQGIS",[183,1372,1373],{},[30,1374,1376],{"href":1375},"\u002Fspatial-data-processing-automation\u002Fweb-services-and-remote-data\u002Fload-wfs-layer-pyqgis\u002F","Load a WFS Layer in PyQGIS",[183,1378,1379],{},[30,1380,1382],{"href":1381},"\u002Fspatial-data-processing-automation\u002Fcoordinate-reference-systems\u002F","Coordinate Reference Systems in PyQGIS",[183,1384,1385],{},[30,1386,1388],{"href":1387},"\u002Fpyqgis-cartography-visualization\u002Fmap-canvas-and-image-export\u002Fset-map-canvas-background-color-pyqgis\u002F","Set the Map Canvas Background Colour in PyQGIS",[1390,1391,1392],"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}",{"title":209,"searchDepth":232,"depth":232,"links":1394},[1395,1396,1397,1398,1399,1400,1401,1402,1403,1404,1405],{"id":177,"depth":232,"text":178},{"id":198,"depth":232,"text":199},{"id":319,"depth":232,"text":320},{"id":630,"depth":232,"text":631},{"id":806,"depth":232,"text":807},{"id":1020,"depth":232,"text":1021},{"id":1151,"depth":232,"text":1152},{"id":1233,"depth":232,"text":1234},{"id":1290,"depth":232,"text":1291},{"id":1307,"depth":232,"text":1308},{"id":1356,"depth":232,"text":1357},"Build a valid WMS URI from Python, discover the layers and coordinate systems a service offers, handle authentication and transparency, and diagnose the blank-layer failures that look like success.","md",{"slug":1409,"type":1410,"breadcrumb":1411,"datePublished":1412,"dateModified":1412},"load-wms-layer-pyqgis","article","Load a WMS Layer","2026-08-15","\u002Fspatial-data-processing-automation\u002Fweb-services-and-remote-data\u002Fload-wms-layer-pyqgis",{"title":5,"description":1406},"spatial-data-processing-automation\u002Fweb-services-and-remote-data\u002Fload-wms-layer-pyqgis\u002Findex","bxU0hP_wHqAXT_fOFsvbaCEBTxdq713fifF_l8bztOU",1786789586252]