[{"data":1,"prerenderedAt":1876},["ShallowReactive",2],{"doc:\u002Fspatial-data-processing-automation\u002Fweb-services-and-remote-data\u002Fload-ogc-api-features-layer-pyqgis":3},{"id":4,"title":5,"body":6,"description":1865,"extension":1866,"meta":1867,"navigation":286,"path":1872,"seo":1873,"stem":1874,"__hash__":1875},"docs\u002Fspatial-data-processing-automation\u002Fweb-services-and-remote-data\u002Fload-ogc-api-features-layer-pyqgis\u002Findex.md","Load an OGC API Features Layer in PyQGIS",{"type":7,"value":8,"toc":1852},"minimark",[9,13,22,31,171,176,213,217,220,770,780,784,795,934,943,1020,1024,1027,1223,1232,1236,1239,1370,1386,1390,1393,1455,1690,1708,1712,1726,1730,1770,1774,1783,1787,1793,1799,1809,1815,1819,1848],[10,11,5],"h1",{"id":12},"load-an-ogc-api-features-layer-in-pyqgis",[14,15,16,17,21],"p",{},"OGC API – Features is the successor to WFS: the same idea of serving vector features over HTTP, rebuilt around plain REST resources and GeoJSON instead of XML capabilities documents and GML. National mapping agencies, statistics offices and open data platforms increasingly publish through it, and servers such as pygeoapi, ldproxy, GeoServer and QGIS Server all speak it. QGIS reads it through the ",[18,19,20],"code",{},"OAPIF"," provider, and every resource is also ordinary JSON you can fetch from Python — which makes discovery and automation straightforward.",[14,23,24,25,30],{},"This recipe belongs to ",[26,27,29],"a",{"href":28},"\u002Fspatial-data-processing-automation\u002Fweb-services-and-remote-data\u002F","Web Services and Remote Data in PyQGIS",". It walks a server's landing page to find collections, loads a collection as a layer, keeps requests small with extent restriction and paging, handles CRS choices, filters on the server where supported, and copies the result locally.",[14,32,33],{},[34,35,40,44,48,55,72,81,90,95,103,107,114,118,122,127,130,133,137,143,148,152,156,162,168],"svg",{"viewBox":36,"role":37,"ariaLabel":38,"xmlns":39},"0 0 760 300","img","The resource tree of an OGC API Features server: a landing page links to conformance and collections, each collection describes its extent and CRS list, and its items resource returns GeoJSON pages that the OAPIF provider turns into a vector layer","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[41,42,43],"title",{},"Landing page → collections → items",[45,46,47],"desc",{},"The landing page at the server root links to a conformance document, listing supported capabilities such as CRS and filtering, and to the collections list. Each collection, for example buildings, has a metadata document with title, spatial extent and supported CRSs. Its items endpoint returns pages of GeoJSON features with next links. The OAPIF provider in QGIS follows the same links and exposes the collection as a vector layer.",[49,50],"rect",{"x":51,"y":51,"width":52,"height":53,"fill":54},"0","760","300","#f6f3ea",[56,57,58],"defs",{},[59,60,67],"marker",{"id":61,"viewBox":62,"refX":63,"refY":64,"markerWidth":65,"markerHeight":65,"orient":66},"oapTreeArrow","0 0 10 10","8","5","7","auto-start-reverse",[68,69],"path",{"d":70,"fill":71},"M0 0 L10 5 L0 10 z","#2f3b35",[73,74,80],"text",{"x":75,"y":76,"style":77,"fill":78,"textAnchor":79},"380","28","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","Everything is a link you can follow",[49,82],{"x":83,"y":84,"width":85,"height":86,"rx":63,"fill":87,"stroke":88,"style":89},"290","46","180","42","#fffdf7","#59645f","stroke-width:2",[73,91,94],{"x":75,"y":92,"style":93,"fill":71,"textAnchor":79},"72","text-anchor:middle;font-size:10.5px;font-family:monospace","\u002F  landing page",[96,97],"line",{"x1":98,"y1":99,"x2":100,"y2":101,"stroke":71,"style":102},"330","88","170","118","stroke-width:1.6;marker-end:url(#oapTreeArrow)",[96,104],{"x1":105,"y1":99,"x2":106,"y2":101,"stroke":71,"style":102},"430","570",[49,108],{"x":109,"y":110,"width":111,"height":109,"rx":63,"fill":112,"stroke":113,"style":89},"60","124","220","#eef7f4","#0f766e",[73,115,117],{"x":100,"y":116,"style":93,"fill":71,"textAnchor":79},"150","\u002Fconformance",[73,119,121],{"x":100,"y":100,"style":120,"fill":88,"textAnchor":79},"text-anchor:middle;font-size:9.5px;font-family:sans-serif","CRS? filtering? paging?",[49,123],{"x":124,"y":110,"width":111,"height":109,"rx":63,"fill":125,"stroke":126,"style":89},"460","#eff3ff","#2563eb",[73,128,129],{"x":106,"y":116,"style":93,"fill":71,"textAnchor":79},"\u002Fcollections",[73,131,132],{"x":106,"y":100,"style":120,"fill":88,"textAnchor":79},"id, title, extent, crs list",[96,134],{"x1":106,"y1":135,"x2":106,"y2":136,"stroke":71,"style":102},"184","210",[49,138],{"x":105,"y":139,"width":140,"height":109,"rx":63,"fill":141,"stroke":142,"style":89},"216","280","#fdf2e2","#b45309",[73,144,147],{"x":106,"y":145,"style":146,"fill":71,"textAnchor":79},"242","text-anchor:middle;font-size:10px;font-family:monospace","\u002Fcollections\u002Fbuildings\u002Fitems",[73,149,151],{"x":106,"y":150,"style":120,"fill":88,"textAnchor":79},"262","GeoJSON pages with next links",[96,153],{"x1":105,"y1":154,"x2":53,"y2":154,"stroke":71,"style":155},"246","stroke-width:1.8;marker-end:url(#oapTreeArrow)",[49,157],{"x":109,"y":139,"width":158,"height":109,"rx":63,"fill":159,"stroke":160,"style":161},"232","#e8efe6","#15803d","stroke-width:2.5",[73,163,167],{"x":164,"y":145,"style":165,"fill":166,"textAnchor":79},"176","text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif","#166534","OAPIF provider",[73,169,170],{"x":164,"y":150,"style":120,"fill":71,"textAnchor":79},"a QgsVectorLayer per collection",[172,173,175],"h2",{"id":174},"prerequisites","Prerequisites",[177,178,179,187,206],"ul",{},[180,181,182,186],"li",{},[183,184,185],"strong",{},"QGIS 3.40 LTR"," or newer, or the QGIS 4 series.",[180,188,189,190,193,194,197,198,201,202,205],{},"The landing page URL of a server, such as ",[18,191,192],{},"https:\u002F\u002Fapi.example.org\u002Fogcapi",". A browser opening it with ",[18,195,196],{},"?f=json"," should show links to ",[18,199,200],{},"conformance"," and ",[18,203,204],{},"collections",".",[180,207,208,209,205],{},"For secured servers, an authentication configuration, as for ",[26,210,212],{"href":211},"\u002Fspatial-data-processing-automation\u002Fweb-services-and-remote-data\u002Fload-wfs-layer-pyqgis\u002F","loading a WFS layer",[172,214,216],{"id":215},"discover-collections-from-the-landing-page","Discover collections from the landing page",[14,218,219],{},"Unlike WFS, there is no single capabilities document to parse. The collections resource lists what the server offers, with enough metadata to choose one — and the conformance resource says which optional capabilities, such as other CRSs or filtering, the server supports.",[221,222,227],"pre",{"className":223,"code":224,"language":225,"meta":226,"style":226},"language-python shiki shiki-themes github-dark","import json\nfrom qgis.PyQt.QtCore import QUrl\nfrom qgis.PyQt.QtNetwork import QNetworkRequest\nfrom qgis.core import QgsBlockingNetworkRequest\n\nROOT = \"https:\u002F\u002Fapi.example.org\u002Fogcapi\"\n\ndef get_json(path, **params):\n    url = QUrl(f\"{ROOT}{path}\")\n    url.setQuery(\"&\".join(f\"{k}={v}\" for k, v in {\"f\": \"json\", **params}.items()))\n    req = QNetworkRequest(url)\n    req.setRawHeader(b\"Accept\", b\"application\u002Fjson\")\n    blocking = QgsBlockingNetworkRequest()\n    if blocking.get(req) != QgsBlockingNetworkRequest.NoError:\n        raise RuntimeError(blocking.errorMessage())\n    return json.loads(bytes(blocking.reply().content()))\n\nconforms = get_json(\"\u002Fconformance\")[\"conformsTo\"]\nsupports_crs = any(\"ogcapi-features-2\" in c and \"crs\" in c for c in conforms)\nsupports_filter = any(\"filter\" in c for c in conforms)\nprint(\"CRS extension:\", supports_crs, \"| filtering:\", supports_filter)\n\nfor col in get_json(\"\u002Fcollections\")[\"collections\"]:\n    bbox = col.get(\"extent\", {}).get(\"spatial\", {}).get(\"bbox\", [[None]])[0]\n    print(f\"{col['id']:\u003C28} {col.get('title', ''):\u003C40} crs={len(col.get('crs', []))} bbox={bbox}\")\n","python","",[18,228,229,241,255,268,281,288,302,307,326,357,421,432,453,464,479,491,506,511,534,577,604,624,629,652,690],{"__ignoreMap":226},[230,231,233,237],"span",{"class":96,"line":232},1,[230,234,236],{"class":235},"snl16","import",[230,238,240],{"class":239},"s95oV"," json\n",[230,242,244,247,250,252],{"class":96,"line":243},2,[230,245,246],{"class":235},"from",[230,248,249],{"class":239}," qgis.PyQt.QtCore ",[230,251,236],{"class":235},[230,253,254],{"class":239}," QUrl\n",[230,256,258,260,263,265],{"class":96,"line":257},3,[230,259,246],{"class":235},[230,261,262],{"class":239}," qgis.PyQt.QtNetwork ",[230,264,236],{"class":235},[230,266,267],{"class":239}," QNetworkRequest\n",[230,269,271,273,276,278],{"class":96,"line":270},4,[230,272,246],{"class":235},[230,274,275],{"class":239}," qgis.core ",[230,277,236],{"class":235},[230,279,280],{"class":239}," QgsBlockingNetworkRequest\n",[230,282,284],{"class":96,"line":283},5,[230,285,287],{"emptyLinePlaceholder":286},true,"\n",[230,289,291,295,298],{"class":96,"line":290},6,[230,292,294],{"class":293},"sDLfK","ROOT",[230,296,297],{"class":235}," =",[230,299,301],{"class":300},"sU2Wk"," \"https:\u002F\u002Fapi.example.org\u002Fogcapi\"\n",[230,303,305],{"class":96,"line":304},7,[230,306,287],{"emptyLinePlaceholder":286},[230,308,310,313,317,320,323],{"class":96,"line":309},8,[230,311,312],{"class":235},"def",[230,314,316],{"class":315},"svObZ"," get_json",[230,318,319],{"class":239},"(path, ",[230,321,322],{"class":235},"**",[230,324,325],{"class":239},"params):\n",[230,327,329,332,335,338,341,344,347,349,352,354],{"class":96,"line":328},9,[230,330,331],{"class":239},"    url ",[230,333,334],{"class":235},"=",[230,336,337],{"class":239}," QUrl(",[230,339,340],{"class":235},"f",[230,342,343],{"class":300},"\"",[230,345,346],{"class":293},"{ROOT}{",[230,348,68],{"class":239},[230,350,351],{"class":293},"}",[230,353,343],{"class":300},[230,355,356],{"class":239},")\n",[230,358,360,363,366,369,371,373,376,379,381,383,385,388,390,392,395,398,401,404,407,410,413,416,418],{"class":96,"line":359},10,[230,361,362],{"class":239},"    url.setQuery(",[230,364,365],{"class":300},"\"&\"",[230,367,368],{"class":239},".join(",[230,370,340],{"class":235},[230,372,343],{"class":300},[230,374,375],{"class":293},"{",[230,377,378],{"class":239},"k",[230,380,351],{"class":293},[230,382,334],{"class":300},[230,384,375],{"class":293},[230,386,387],{"class":239},"v",[230,389,351],{"class":293},[230,391,343],{"class":300},[230,393,394],{"class":235}," for",[230,396,397],{"class":239}," k, v ",[230,399,400],{"class":235},"in",[230,402,403],{"class":239}," {",[230,405,406],{"class":300},"\"f\"",[230,408,409],{"class":239},": ",[230,411,412],{"class":300},"\"json\"",[230,414,415],{"class":239},", ",[230,417,322],{"class":235},[230,419,420],{"class":239},"params}.items()))\n",[230,422,424,427,429],{"class":96,"line":423},11,[230,425,426],{"class":239},"    req ",[230,428,334],{"class":235},[230,430,431],{"class":239}," QNetworkRequest(url)\n",[230,433,435,438,441,444,446,448,451],{"class":96,"line":434},12,[230,436,437],{"class":239},"    req.setRawHeader(",[230,439,440],{"class":235},"b",[230,442,443],{"class":300},"\"Accept\"",[230,445,415],{"class":239},[230,447,440],{"class":235},[230,449,450],{"class":300},"\"application\u002Fjson\"",[230,452,356],{"class":239},[230,454,456,459,461],{"class":96,"line":455},13,[230,457,458],{"class":239},"    blocking ",[230,460,334],{"class":235},[230,462,463],{"class":239}," QgsBlockingNetworkRequest()\n",[230,465,467,470,473,476],{"class":96,"line":466},14,[230,468,469],{"class":235},"    if",[230,471,472],{"class":239}," blocking.get(req) ",[230,474,475],{"class":235},"!=",[230,477,478],{"class":239}," QgsBlockingNetworkRequest.NoError:\n",[230,480,482,485,488],{"class":96,"line":481},15,[230,483,484],{"class":235},"        raise",[230,486,487],{"class":293}," RuntimeError",[230,489,490],{"class":239},"(blocking.errorMessage())\n",[230,492,494,497,500,503],{"class":96,"line":493},16,[230,495,496],{"class":235},"    return",[230,498,499],{"class":239}," json.loads(",[230,501,502],{"class":293},"bytes",[230,504,505],{"class":239},"(blocking.reply().content()))\n",[230,507,509],{"class":96,"line":508},17,[230,510,287],{"emptyLinePlaceholder":286},[230,512,514,517,519,522,525,528,531],{"class":96,"line":513},18,[230,515,516],{"class":239},"conforms ",[230,518,334],{"class":235},[230,520,521],{"class":239}," get_json(",[230,523,524],{"class":300},"\"\u002Fconformance\"",[230,526,527],{"class":239},")[",[230,529,530],{"class":300},"\"conformsTo\"",[230,532,533],{"class":239},"]\n",[230,535,537,540,542,545,548,551,554,557,560,563,565,567,570,572,574],{"class":96,"line":536},19,[230,538,539],{"class":239},"supports_crs ",[230,541,334],{"class":235},[230,543,544],{"class":293}," any",[230,546,547],{"class":239},"(",[230,549,550],{"class":300},"\"ogcapi-features-2\"",[230,552,553],{"class":235}," in",[230,555,556],{"class":239}," c ",[230,558,559],{"class":235},"and",[230,561,562],{"class":300}," \"crs\"",[230,564,553],{"class":235},[230,566,556],{"class":239},[230,568,569],{"class":235},"for",[230,571,556],{"class":239},[230,573,400],{"class":235},[230,575,576],{"class":239}," conforms)\n",[230,578,580,583,585,587,589,592,594,596,598,600,602],{"class":96,"line":579},20,[230,581,582],{"class":239},"supports_filter ",[230,584,334],{"class":235},[230,586,544],{"class":293},[230,588,547],{"class":239},[230,590,591],{"class":300},"\"filter\"",[230,593,553],{"class":235},[230,595,556],{"class":239},[230,597,569],{"class":235},[230,599,556],{"class":239},[230,601,400],{"class":235},[230,603,576],{"class":239},[230,605,607,610,612,615,618,621],{"class":96,"line":606},21,[230,608,609],{"class":293},"print",[230,611,547],{"class":239},[230,613,614],{"class":300},"\"CRS extension:\"",[230,616,617],{"class":239},", supports_crs, ",[230,619,620],{"class":300},"\"| filtering:\"",[230,622,623],{"class":239},", supports_filter)\n",[230,625,627],{"class":96,"line":626},22,[230,628,287],{"emptyLinePlaceholder":286},[230,630,632,634,637,639,641,644,646,649],{"class":96,"line":631},23,[230,633,569],{"class":235},[230,635,636],{"class":239}," col ",[230,638,400],{"class":235},[230,640,521],{"class":239},[230,642,643],{"class":300},"\"\u002Fcollections\"",[230,645,527],{"class":239},[230,647,648],{"class":300},"\"collections\"",[230,650,651],{"class":239},"]:\n",[230,653,655,658,660,663,666,669,672,674,677,680,683,686,688],{"class":96,"line":654},24,[230,656,657],{"class":239},"    bbox ",[230,659,334],{"class":235},[230,661,662],{"class":239}," col.get(",[230,664,665],{"class":300},"\"extent\"",[230,667,668],{"class":239},", {}).get(",[230,670,671],{"class":300},"\"spatial\"",[230,673,668],{"class":239},[230,675,676],{"class":300},"\"bbox\"",[230,678,679],{"class":239},", [[",[230,681,682],{"class":293},"None",[230,684,685],{"class":239},"]])[",[230,687,51],{"class":293},[230,689,533],{"class":239},[230,691,693,696,698,700,702,704,707,710,713,716,718,720,723,726,728,731,734,737,739,742,745,748,751,754,756,759,761,764,766,768],{"class":96,"line":692},25,[230,694,695],{"class":293},"    print",[230,697,547],{"class":239},[230,699,340],{"class":235},[230,701,343],{"class":300},[230,703,375],{"class":293},[230,705,706],{"class":239},"col[",[230,708,709],{"class":300},"'id'",[230,711,712],{"class":239},"]",[230,714,715],{"class":235},":\u003C28",[230,717,351],{"class":293},[230,719,403],{"class":293},[230,721,722],{"class":239},"col.get(",[230,724,725],{"class":300},"'title'",[230,727,415],{"class":239},[230,729,730],{"class":300},"''",[230,732,733],{"class":239},")",[230,735,736],{"class":235},":\u003C40",[230,738,351],{"class":293},[230,740,741],{"class":300}," crs=",[230,743,744],{"class":293},"{len",[230,746,747],{"class":239},"(col.get(",[230,749,750],{"class":300},"'crs'",[230,752,753],{"class":239},", []))",[230,755,351],{"class":293},[230,757,758],{"class":300}," bbox=",[230,760,375],{"class":293},[230,762,763],{"class":239},"bbox",[230,765,351],{"class":293},[230,767,343],{"class":300},[230,769,356],{"class":239},[14,771,772,775,776,779],{},[183,773,774],{},"Breakdown:"," The conformance list is a set of URIs naming the parts of the standard the server implements; Part 2 adds coordinate reference systems beyond WGS 84 longitude\u002Flatitude, and Part 3 adds filtering. Checking them before building layers avoids asking a server for something it will silently ignore. The collection ",[18,777,778],{},"id"," is what the provider needs; the title is for humans. The spatial extent bbox is always expressed in CRS84 longitude, latitude order, whatever CRS the data is stored in.",[172,781,783],{"id":782},"load-a-collection-as-a-layer","Load a collection as a layer",[14,785,786,787,790,791,794],{},"The provider takes the landing page URL and a collection id as ",[18,788,789],{},"typename",". Building the URI with ",[18,792,793],{},"QgsDataSourceUri"," keeps the quoting right.",[221,796,798],{"className":223,"code":797,"language":225,"meta":226,"style":226},"from qgis.core import QgsDataSourceUri, QgsVectorLayer, QgsProject\n\nuri = QgsDataSourceUri()\nuri.setParam(\"url\", ROOT)\nuri.setParam(\"typename\", \"buildings\")\nuri.setParam(\"restrictToRequestBBOX\", \"1\")\n\nbuildings = QgsVectorLayer(uri.uri(False), \"buildings (OGC API)\", \"OAPIF\")\nif not buildings.isValid():\n    raise RuntimeError(buildings.error().summary())\n\nprint(buildings.crs().authid(), buildings.wkbType(), buildings.fields().names())\nQgsProject.instance().addMapLayer(buildings)\n",[18,799,800,811,815,825,839,853,867,871,897,908,918,922,929],{"__ignoreMap":226},[230,801,802,804,806,808],{"class":96,"line":232},[230,803,246],{"class":235},[230,805,275],{"class":239},[230,807,236],{"class":235},[230,809,810],{"class":239}," QgsDataSourceUri, QgsVectorLayer, QgsProject\n",[230,812,813],{"class":96,"line":243},[230,814,287],{"emptyLinePlaceholder":286},[230,816,817,820,822],{"class":96,"line":257},[230,818,819],{"class":239},"uri ",[230,821,334],{"class":235},[230,823,824],{"class":239}," QgsDataSourceUri()\n",[230,826,827,830,833,835,837],{"class":96,"line":270},[230,828,829],{"class":239},"uri.setParam(",[230,831,832],{"class":300},"\"url\"",[230,834,415],{"class":239},[230,836,294],{"class":293},[230,838,356],{"class":239},[230,840,841,843,846,848,851],{"class":96,"line":283},[230,842,829],{"class":239},[230,844,845],{"class":300},"\"typename\"",[230,847,415],{"class":239},[230,849,850],{"class":300},"\"buildings\"",[230,852,356],{"class":239},[230,854,855,857,860,862,865],{"class":96,"line":290},[230,856,829],{"class":239},[230,858,859],{"class":300},"\"restrictToRequestBBOX\"",[230,861,415],{"class":239},[230,863,864],{"class":300},"\"1\"",[230,866,356],{"class":239},[230,868,869],{"class":96,"line":304},[230,870,287],{"emptyLinePlaceholder":286},[230,872,873,876,878,881,884,887,890,892,895],{"class":96,"line":309},[230,874,875],{"class":239},"buildings ",[230,877,334],{"class":235},[230,879,880],{"class":239}," QgsVectorLayer(uri.uri(",[230,882,883],{"class":293},"False",[230,885,886],{"class":239},"), ",[230,888,889],{"class":300},"\"buildings (OGC API)\"",[230,891,415],{"class":239},[230,893,894],{"class":300},"\"OAPIF\"",[230,896,356],{"class":239},[230,898,899,902,905],{"class":96,"line":328},[230,900,901],{"class":235},"if",[230,903,904],{"class":235}," not",[230,906,907],{"class":239}," buildings.isValid():\n",[230,909,910,913,915],{"class":96,"line":359},[230,911,912],{"class":235},"    raise",[230,914,487],{"class":293},[230,916,917],{"class":239},"(buildings.error().summary())\n",[230,919,920],{"class":96,"line":423},[230,921,287],{"emptyLinePlaceholder":286},[230,923,924,926],{"class":96,"line":434},[230,925,609],{"class":293},[230,927,928],{"class":239},"(buildings.crs().authid(), buildings.wkbType(), buildings.fields().names())\n",[230,930,931],{"class":96,"line":455},[230,932,933],{"class":239},"QgsProject.instance().addMapLayer(buildings)\n",[14,935,936,938,939,942],{},[183,937,774],{}," ",[18,940,941],{},"restrictToRequestBBOX=1"," makes the provider request only features in the current map view, re-requesting as you pan, which is the only sensible mode for a national buildings collection with millions of items. Without it, the provider pages through the entire collection on first access. Field names and types are inferred from the collection's schema when the server publishes one, or from the first page of features otherwise, so a property that is null in every early feature may arrive typed as a string.",[14,944,945],{},[34,946,949,952,955,958,962,969,975,979,984,988,991,994,997,1000,1003,1007,1011,1014,1017],{"viewBox":947,"role":37,"ariaLabel":948,"xmlns":39},"0 0 760 256","Axis order and CRS in OGC API Features: by default coordinates are CRS84 longitude then latitude, a server supporting Part 2 can return other CRSs in their authority axis order, and QGIS handles the swap when the CRS is declared correctly",[41,950,951],{},"CRS84 by default, others by agreement",[45,953,954],{},"Left: default output in CRS84 with longitude first, for example 13.40, 52.52 for Berlin, which QGIS reads as EPSG:4326 with the correct axis order. Middle: a server supporting the CRS extension returning EPSG:25833 metres when asked, avoiding local reprojection. Right: a misconfigured server that labels latitude-first coordinates as CRS84, placing Berlin in the Indian Ocean; the fix is on the server side or by swapping axes.",[49,956],{"x":51,"y":51,"width":52,"height":957,"fill":54},"256",[73,959,961],{"x":75,"y":960,"style":77,"fill":78,"textAnchor":79},"26","Know which CRS, and which axis first",[49,963],{"x":964,"y":965,"width":966,"height":967,"rx":968,"fill":159,"stroke":160,"style":161},"24","44","222","192","10",[73,970,974],{"x":971,"y":972,"style":973,"fill":166,"textAnchor":79},"135","70","text-anchor:middle;font-size:11.5px;font-weight:bold;font-family:sans-serif","default: CRS84",[73,976,978],{"x":971,"y":977,"style":93,"fill":71,"textAnchor":79},"104","13.40, 52.52",[73,980,983],{"x":971,"y":981,"style":982,"fill":71,"textAnchor":79},"128","text-anchor:middle;font-size:10px;font-family:sans-serif","longitude first",[73,985,987],{"x":971,"y":986,"style":982,"fill":88,"textAnchor":79},"190","always supported",[49,989],{"x":990,"y":965,"width":966,"height":967,"rx":968,"fill":125,"stroke":126,"style":161},"269",[73,992,993],{"x":75,"y":972,"style":973,"fill":126,"textAnchor":79},"Part 2: requested CRS",[73,995,996],{"x":75,"y":977,"style":93,"fill":71,"textAnchor":79},"391,779 · 5,820,068",[73,998,999],{"x":75,"y":981,"style":982,"fill":71,"textAnchor":79},"EPSG:25833 metres",[73,1001,1002],{"x":75,"y":986,"style":982,"fill":88,"textAnchor":79},"only if the server lists it",[49,1004],{"x":1005,"y":965,"width":966,"height":967,"rx":968,"fill":141,"stroke":1006,"style":161},"514","#b91c1c",[73,1008,1010],{"x":1009,"y":972,"style":973,"fill":1006,"textAnchor":79},"625","mislabelled server",[73,1012,1013],{"x":1009,"y":977,"style":93,"fill":71,"textAnchor":79},"52.52, 13.40",[73,1015,1016],{"x":1009,"y":981,"style":982,"fill":71,"textAnchor":79},"latitude first, called CRS84",[73,1018,1019],{"x":1009,"y":986,"style":982,"fill":1006,"textAnchor":79},"lands in the ocean",[172,1021,1023],{"id":1022},"choose-the-crs","Choose the CRS",[14,1025,1026],{},"By default every OGC API Features server returns WGS 84 longitude\u002Flatitude (CRS84). Servers that implement Part 2 can return other CRSs listed in the collection metadata, which saves reprojecting every feature locally and preserves the precision of projected source data.",[221,1028,1030],{"className":223,"code":1029,"language":225,"meta":226,"style":226},"collection = get_json(\"\u002Fcollections\u002Fbuildings\")\noffered = collection.get(\"crs\", [])\nprint(\"offered:\", offered[:5], \"…\" if len(offered) > 5 else \"\")\n\nwanted = \"http:\u002F\u002Fwww.opengis.net\u002Fdef\u002Fcrs\u002FEPSG\u002F0\u002F25833\"\nuri = QgsDataSourceUri()\nuri.setParam(\"url\", ROOT)\nuri.setParam(\"typename\", \"buildings\")\nuri.setParam(\"restrictToRequestBBOX\", \"1\")\nif wanted in offered:\n    uri.setParam(\"srsname\", \"EPSG:25833\")\n\nbuildings_utm = QgsVectorLayer(uri.uri(False), \"buildings (UTM 33N)\", \"OAPIF\")\nprint(buildings_utm.crs().authid())\n",[18,1031,1032,1046,1062,1105,1109,1119,1127,1139,1151,1163,1175,1190,1194,1216],{"__ignoreMap":226},[230,1033,1034,1037,1039,1041,1044],{"class":96,"line":232},[230,1035,1036],{"class":239},"collection ",[230,1038,334],{"class":235},[230,1040,521],{"class":239},[230,1042,1043],{"class":300},"\"\u002Fcollections\u002Fbuildings\"",[230,1045,356],{"class":239},[230,1047,1048,1051,1053,1056,1059],{"class":96,"line":243},[230,1049,1050],{"class":239},"offered ",[230,1052,334],{"class":235},[230,1054,1055],{"class":239}," collection.get(",[230,1057,1058],{"class":300},"\"crs\"",[230,1060,1061],{"class":239},", [])\n",[230,1063,1064,1066,1068,1071,1074,1076,1079,1082,1085,1088,1091,1094,1097,1100,1103],{"class":96,"line":257},[230,1065,609],{"class":293},[230,1067,547],{"class":239},[230,1069,1070],{"class":300},"\"offered:\"",[230,1072,1073],{"class":239},", offered[:",[230,1075,64],{"class":293},[230,1077,1078],{"class":239},"], ",[230,1080,1081],{"class":300},"\"…\"",[230,1083,1084],{"class":235}," if",[230,1086,1087],{"class":293}," len",[230,1089,1090],{"class":239},"(offered) ",[230,1092,1093],{"class":235},">",[230,1095,1096],{"class":293}," 5",[230,1098,1099],{"class":235}," else",[230,1101,1102],{"class":300}," \"\"",[230,1104,356],{"class":239},[230,1106,1107],{"class":96,"line":270},[230,1108,287],{"emptyLinePlaceholder":286},[230,1110,1111,1114,1116],{"class":96,"line":283},[230,1112,1113],{"class":239},"wanted ",[230,1115,334],{"class":235},[230,1117,1118],{"class":300}," \"http:\u002F\u002Fwww.opengis.net\u002Fdef\u002Fcrs\u002FEPSG\u002F0\u002F25833\"\n",[230,1120,1121,1123,1125],{"class":96,"line":290},[230,1122,819],{"class":239},[230,1124,334],{"class":235},[230,1126,824],{"class":239},[230,1128,1129,1131,1133,1135,1137],{"class":96,"line":304},[230,1130,829],{"class":239},[230,1132,832],{"class":300},[230,1134,415],{"class":239},[230,1136,294],{"class":293},[230,1138,356],{"class":239},[230,1140,1141,1143,1145,1147,1149],{"class":96,"line":309},[230,1142,829],{"class":239},[230,1144,845],{"class":300},[230,1146,415],{"class":239},[230,1148,850],{"class":300},[230,1150,356],{"class":239},[230,1152,1153,1155,1157,1159,1161],{"class":96,"line":328},[230,1154,829],{"class":239},[230,1156,859],{"class":300},[230,1158,415],{"class":239},[230,1160,864],{"class":300},[230,1162,356],{"class":239},[230,1164,1165,1167,1170,1172],{"class":96,"line":359},[230,1166,901],{"class":235},[230,1168,1169],{"class":239}," wanted ",[230,1171,400],{"class":235},[230,1173,1174],{"class":239}," offered:\n",[230,1176,1177,1180,1183,1185,1188],{"class":96,"line":423},[230,1178,1179],{"class":239},"    uri.setParam(",[230,1181,1182],{"class":300},"\"srsname\"",[230,1184,415],{"class":239},[230,1186,1187],{"class":300},"\"EPSG:25833\"",[230,1189,356],{"class":239},[230,1191,1192],{"class":96,"line":434},[230,1193,287],{"emptyLinePlaceholder":286},[230,1195,1196,1199,1201,1203,1205,1207,1210,1212,1214],{"class":96,"line":455},[230,1197,1198],{"class":239},"buildings_utm ",[230,1200,334],{"class":235},[230,1202,880],{"class":239},[230,1204,883],{"class":293},[230,1206,886],{"class":239},[230,1208,1209],{"class":300},"\"buildings (UTM 33N)\"",[230,1211,415],{"class":239},[230,1213,894],{"class":300},[230,1215,356],{"class":239},[230,1217,1218,1220],{"class":96,"line":466},[230,1219,609],{"class":293},[230,1221,1222],{"class":239},"(buildings_utm.crs().authid())\n",[14,1224,1225,1227,1228,1231],{},[183,1226,774],{}," CRSs are identified by OGC URIs in the collection metadata; the provider accepts the familiar ",[18,1229,1230],{},"EPSG:"," form. Only request a CRS the collection lists — an unsupported request typically falls back to CRS84 without an error, which is harmless for display and confusing for anyone expecting metres. If features appear mirrored across the equator or in the ocean, the server is emitting latitude-first coordinates while claiming CRS84; that is a server bug, and a quick check against a known location before any analysis is worth the thirty seconds.",[172,1233,1235],{"id":1234},"filter-on-the-server-and-page-sensibly","Filter on the server and page sensibly",[14,1237,1238],{},"Pulling a whole collection to throw most of it away wastes the server's time and yours. Where the server supports filtering, a subset string can be pushed to it; where it does not, restrict by extent and filter locally.",[221,1240,1242],{"className":223,"code":1241,"language":225,"meta":226,"style":226},"from qgis.core import QgsRectangle, QgsFeatureRequest\n\nbuildings.setSubsetString(\"\\\"building_use\\\" = 'school'\")\nprint(\"valid after filter:\", buildings.isValid())\n\narea = QgsRectangle(13.35, 52.49, 13.45, 52.55)\nrequest = QgsFeatureRequest().setFilterRect(area).setLimit(5000)\nschools = list(buildings.getFeatures(request))\nprint(len(schools), \"school buildings in the area\")\n",[18,1243,1244,1255,1259,1279,1291,1295,1325,1340,1353],{"__ignoreMap":226},[230,1245,1246,1248,1250,1252],{"class":96,"line":232},[230,1247,246],{"class":235},[230,1249,275],{"class":239},[230,1251,236],{"class":235},[230,1253,1254],{"class":239}," QgsRectangle, QgsFeatureRequest\n",[230,1256,1257],{"class":96,"line":243},[230,1258,287],{"emptyLinePlaceholder":286},[230,1260,1261,1264,1266,1269,1272,1274,1277],{"class":96,"line":257},[230,1262,1263],{"class":239},"buildings.setSubsetString(",[230,1265,343],{"class":300},[230,1267,1268],{"class":293},"\\\"",[230,1270,1271],{"class":300},"building_use",[230,1273,1268],{"class":293},[230,1275,1276],{"class":300}," = 'school'\"",[230,1278,356],{"class":239},[230,1280,1281,1283,1285,1288],{"class":96,"line":270},[230,1282,609],{"class":293},[230,1284,547],{"class":239},[230,1286,1287],{"class":300},"\"valid after filter:\"",[230,1289,1290],{"class":239},", buildings.isValid())\n",[230,1292,1293],{"class":96,"line":283},[230,1294,287],{"emptyLinePlaceholder":286},[230,1296,1297,1300,1302,1305,1308,1310,1313,1315,1318,1320,1323],{"class":96,"line":290},[230,1298,1299],{"class":239},"area ",[230,1301,334],{"class":235},[230,1303,1304],{"class":239}," QgsRectangle(",[230,1306,1307],{"class":293},"13.35",[230,1309,415],{"class":239},[230,1311,1312],{"class":293},"52.49",[230,1314,415],{"class":239},[230,1316,1317],{"class":293},"13.45",[230,1319,415],{"class":239},[230,1321,1322],{"class":293},"52.55",[230,1324,356],{"class":239},[230,1326,1327,1330,1332,1335,1338],{"class":96,"line":304},[230,1328,1329],{"class":239},"request ",[230,1331,334],{"class":235},[230,1333,1334],{"class":239}," QgsFeatureRequest().setFilterRect(area).setLimit(",[230,1336,1337],{"class":293},"5000",[230,1339,356],{"class":239},[230,1341,1342,1345,1347,1350],{"class":96,"line":309},[230,1343,1344],{"class":239},"schools ",[230,1346,334],{"class":235},[230,1348,1349],{"class":293}," list",[230,1351,1352],{"class":239},"(buildings.getFeatures(request))\n",[230,1354,1355,1357,1359,1362,1365,1368],{"class":96,"line":328},[230,1356,609],{"class":293},[230,1358,547],{"class":239},[230,1360,1361],{"class":293},"len",[230,1363,1364],{"class":239},"(schools), ",[230,1366,1367],{"class":300},"\"school buildings in the area\"",[230,1369,356],{"class":239},[14,1371,1372,1374,1375,1378,1379,1381,1382,1385],{},[183,1373,774],{}," On recent QGIS releases, when the server advertises filtering support, the provider translates simple subset expressions into a server-side filter so only matching items are transferred; on servers without it the subset is applied locally after download. Either way the result is correct, and only the amount of data transferred differs — so combining a subset with an extent filter is always safe. ",[18,1376,1377],{},"setFilterRect"," becomes a ",[18,1380,763],{}," parameter on the items request. ",[18,1383,1384],{},"setLimit"," protects a script from an unexpectedly huge result, which is good practice against any remote source.",[172,1387,1389],{"id":1388},"take-a-local-snapshot","Take a local snapshot",[14,1391,1392],{},"For analysis, copy the filtered features to a GeoPackage and record where and when they came from — the same discipline as for any web service.",[14,1394,1395],{},[34,1396,1399,1402,1405,1407,1410,1414,1419,1424,1428,1432,1435,1439,1442,1445,1448,1452],{"viewBox":1397,"role":37,"ariaLabel":1398,"xmlns":39},"0 0 760 232","OGC API Features compared with WFS for a scripting workflow: JSON resources and GeoJSON instead of XML and GML, links instead of a capabilities document, and the same local snapshot step at the end",[41,1400,1401],{},"OGC API Features and WFS side by side",[45,1403,1404],{},"WFS: a GetCapabilities XML document, GetFeature requests returning GML, parameters in key value pairs, and paging with startIndex. OGC API Features: a JSON landing page and collections list, items returned as GeoJSON, REST paths, and paging by following next links. Both end with the same step in PyQGIS: load through a provider and save a local GeoPackage snapshot for analysis.",[49,1406],{"x":51,"y":51,"width":52,"height":158,"fill":54},[73,1408,1409],{"x":75,"y":960,"style":77,"fill":78,"textAnchor":79},"Different protocol, same end of the pipeline",[49,1411],{"x":964,"y":965,"width":1412,"height":1413,"rx":968,"fill":87,"stroke":88,"style":89},"344","130",[73,1415,1418],{"x":1416,"y":972,"style":1417,"fill":78,"textAnchor":79},"196","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","WFS",[73,1420,1423],{"x":1416,"y":1421,"style":1422,"fill":71,"textAnchor":79},"98","text-anchor:middle;font-size:10.5px;font-family:sans-serif","GetCapabilities XML",[73,1425,1427],{"x":1416,"y":1426,"style":1422,"fill":71,"textAnchor":79},"120","GetFeature → GML",[73,1429,1431],{"x":1416,"y":1430,"style":1422,"fill":71,"textAnchor":79},"142","paging via startIndex",[49,1433],{"x":1434,"y":965,"width":1412,"height":1413,"rx":968,"fill":87,"stroke":126,"style":89},"392",[73,1436,1438],{"x":1437,"y":972,"style":1417,"fill":126,"textAnchor":79},"564","OGC API – Features",[73,1440,1441],{"x":1437,"y":1421,"style":1422,"fill":71,"textAnchor":79},"JSON landing page + collections",[73,1443,1444],{"x":1437,"y":1426,"style":1422,"fill":71,"textAnchor":79},"\u002Fitems → GeoJSON",[73,1446,1447],{"x":1437,"y":1430,"style":1422,"fill":71,"textAnchor":79},"paging via next links",[49,1449],{"x":1426,"y":986,"width":1450,"height":1451,"rx":63,"fill":159,"stroke":160,"style":89},"520","30",[73,1453,1454],{"x":75,"y":136,"style":1422,"fill":71,"textAnchor":79},"both: provider layer → native:savefeatures → dated GeoPackage",[221,1456,1458],{"className":223,"code":1457,"language":225,"meta":226,"style":226},"import processing\nfrom datetime import datetime, timezone\n\nbuildings.setSubsetString(\"\\\"building_use\\\" = 'school'\")\nout = processing.run(\"native:savefeatures\", {\n    \"INPUT\": buildings,\n    \"OUTPUT\": \"\u002Fdata\u002Fcache\u002Fschools_ogcapi.gpkg\",\n    \"LAYER_NAME\": \"schools\",\n})[\"OUTPUT\"]\nlocal = QgsVectorLayer(out, \"schools\", \"ogr\")\n\nmd = local.metadata()\nmd.setAbstract(f\"OGC API Features collection 'buildings' at {ROOT}, \"\n               f\"filter building_use = 'school', retrieved \"\n               f\"{datetime.now(timezone.utc):%Y-%m-%d %H:%M} UTC\")\nlocal.setMetadata(md)\nlocal.saveNamedMetadata(out + \"|layername=schools\")\nprint(local.featureCount(), \"features saved\")\n",[18,1459,1460,1467,1479,1483,1499,1515,1523,1536,1548,1558,1577,1581,1591,1607,1615,1660,1665,1678],{"__ignoreMap":226},[230,1461,1462,1464],{"class":96,"line":232},[230,1463,236],{"class":235},[230,1465,1466],{"class":239}," processing\n",[230,1468,1469,1471,1474,1476],{"class":96,"line":243},[230,1470,246],{"class":235},[230,1472,1473],{"class":239}," datetime ",[230,1475,236],{"class":235},[230,1477,1478],{"class":239}," datetime, timezone\n",[230,1480,1481],{"class":96,"line":257},[230,1482,287],{"emptyLinePlaceholder":286},[230,1484,1485,1487,1489,1491,1493,1495,1497],{"class":96,"line":270},[230,1486,1263],{"class":239},[230,1488,343],{"class":300},[230,1490,1268],{"class":293},[230,1492,1271],{"class":300},[230,1494,1268],{"class":293},[230,1496,1276],{"class":300},[230,1498,356],{"class":239},[230,1500,1501,1504,1506,1509,1512],{"class":96,"line":283},[230,1502,1503],{"class":239},"out ",[230,1505,334],{"class":235},[230,1507,1508],{"class":239}," processing.run(",[230,1510,1511],{"class":300},"\"native:savefeatures\"",[230,1513,1514],{"class":239},", {\n",[230,1516,1517,1520],{"class":96,"line":290},[230,1518,1519],{"class":300},"    \"INPUT\"",[230,1521,1522],{"class":239},": buildings,\n",[230,1524,1525,1528,1530,1533],{"class":96,"line":304},[230,1526,1527],{"class":300},"    \"OUTPUT\"",[230,1529,409],{"class":239},[230,1531,1532],{"class":300},"\"\u002Fdata\u002Fcache\u002Fschools_ogcapi.gpkg\"",[230,1534,1535],{"class":239},",\n",[230,1537,1538,1541,1543,1546],{"class":96,"line":309},[230,1539,1540],{"class":300},"    \"LAYER_NAME\"",[230,1542,409],{"class":239},[230,1544,1545],{"class":300},"\"schools\"",[230,1547,1535],{"class":239},[230,1549,1550,1553,1556],{"class":96,"line":328},[230,1551,1552],{"class":239},"})[",[230,1554,1555],{"class":300},"\"OUTPUT\"",[230,1557,533],{"class":239},[230,1559,1560,1563,1565,1568,1570,1572,1575],{"class":96,"line":359},[230,1561,1562],{"class":239},"local ",[230,1564,334],{"class":235},[230,1566,1567],{"class":239}," QgsVectorLayer(out, ",[230,1569,1545],{"class":300},[230,1571,415],{"class":239},[230,1573,1574],{"class":300},"\"ogr\"",[230,1576,356],{"class":239},[230,1578,1579],{"class":96,"line":423},[230,1580,287],{"emptyLinePlaceholder":286},[230,1582,1583,1586,1588],{"class":96,"line":434},[230,1584,1585],{"class":239},"md ",[230,1587,334],{"class":235},[230,1589,1590],{"class":239}," local.metadata()\n",[230,1592,1593,1596,1598,1601,1604],{"class":96,"line":455},[230,1594,1595],{"class":239},"md.setAbstract(",[230,1597,340],{"class":235},[230,1599,1600],{"class":300},"\"OGC API Features collection 'buildings' at ",[230,1602,1603],{"class":293},"{ROOT}",[230,1605,1606],{"class":300},", \"\n",[230,1608,1609,1612],{"class":96,"line":466},[230,1610,1611],{"class":235},"               f",[230,1613,1614],{"class":300},"\"filter building_use = 'school', retrieved \"\n",[230,1616,1617,1619,1621,1623,1626,1629,1632,1635,1638,1640,1643,1645,1648,1650,1653,1655,1658],{"class":96,"line":481},[230,1618,1611],{"class":235},[230,1620,343],{"class":300},[230,1622,375],{"class":293},[230,1624,1625],{"class":239},"datetime.now(timezone.utc):",[230,1627,1628],{"class":235},"%",[230,1630,1631],{"class":239},"Y",[230,1633,1634],{"class":235},"-%",[230,1636,1637],{"class":239},"m",[230,1639,1634],{"class":235},[230,1641,1642],{"class":239},"d ",[230,1644,1628],{"class":235},[230,1646,1647],{"class":239},"H:",[230,1649,1628],{"class":235},[230,1651,1652],{"class":239},"M",[230,1654,351],{"class":293},[230,1656,1657],{"class":300}," UTC\"",[230,1659,356],{"class":239},[230,1661,1662],{"class":96,"line":493},[230,1663,1664],{"class":239},"local.setMetadata(md)\n",[230,1666,1667,1670,1673,1676],{"class":96,"line":508},[230,1668,1669],{"class":239},"local.saveNamedMetadata(out ",[230,1671,1672],{"class":235},"+",[230,1674,1675],{"class":300}," \"|layername=schools\"",[230,1677,356],{"class":239},[230,1679,1680,1682,1685,1688],{"class":96,"line":513},[230,1681,609],{"class":293},[230,1683,1684],{"class":239},"(local.featureCount(), ",[230,1686,1687],{"class":300},"\"features saved\"",[230,1689,356],{"class":239},[14,1691,1692,1694,1695,1698,1699,1703,1704,1707],{},[183,1693,774],{}," Saving from the filtered layer transfers only the matching items. Because ",[18,1696,1697],{},"restrictToRequestBBOX"," only affects map rendering requests, a full save still pages through every matching item in the collection — which is what you want for a snapshot, and why the filter matters. The abstract records the server, the collection, the filter and a UTC timestamp, so a reader of the GeoPackage months later can tell exactly what it represents and re-run the query; the same habit is shown for ",[26,1700,1702],{"href":1701},"\u002Fspatial-data-processing-automation\u002Fweb-services-and-remote-data\u002Fload-arcgis-rest-feature-service-pyqgis\u002F","ArcGIS REST feature services",". Many OGC API servers also publish a ",[18,1705,1706],{},"timeStamp"," on each items response, which is worth copying into the metadata when present, because it describes the data rather than the download.",[172,1709,1711],{"id":1710},"qgis-version-compatibility","QGIS version compatibility",[14,1713,1714,1715,1717,1718,1721,1722,1725],{},"The ",[18,1716,20],{}," provider has been available since QGIS 3.14 (under its original \"WFS 3\" name before that). Support for CRSs other than CRS84, server-side filtering and editing through Part 4 (transactions) arrived progressively in the 3.2x releases; use 3.40 or newer against current servers. ",[18,1719,1720],{},"QgsBlockingNetworkRequest.NoError"," needs its scoped form, ",[18,1723,1724],{},"QgsBlockingNetworkRequest.ErrorCode.NoError",", on the QGIS 4 series.",[172,1727,1729],{"id":1728},"troubleshooting","Troubleshooting",[177,1731,1732,1744,1750,1758,1764],{},[180,1733,1734,938,1737,1740,1741,1743],{},[183,1735,1736],{},"The layer is invalid.",[18,1738,1739],{},"url"," points at the collection or items resource instead of the landing page, or ",[18,1742,789],{}," is the title instead of the id.",[180,1745,1746,1749],{},[183,1747,1748],{},"Features are in the ocean."," The server writes latitude first while claiming CRS84; report it and swap axes locally meanwhile.",[180,1751,1752,938,1755,1757],{},[183,1753,1754],{},"The first draw takes minutes.",[18,1756,1697],{}," is off, so the whole collection is being fetched.",[180,1759,1760,1763],{},[183,1761,1762],{},"Filters have no effect on download size."," The server does not support filtering; the subset is applied locally.",[180,1765,1766,1769],{},[183,1767,1768],{},"Some attributes are strings instead of numbers."," Types were inferred from early features where the value was null.",[172,1771,1773],{"id":1772},"conclusion","Conclusion",[14,1775,1776,1777,1779,1780,1782],{},"Start from the landing page: read conformance to learn what the server supports and the collections list to find ids. Load collections with the ",[18,1778,20],{}," provider and ",[18,1781,1697],{},", request a projected CRS only when the collection offers it, filter on the server where possible, and snapshot to GeoPackage for analysis.",[172,1784,1786],{"id":1785},"frequently-asked-questions","Frequently Asked Questions",[14,1788,1789,1792],{},[183,1790,1791],{},"Is OGC API Features replacing WFS?","\nFor new publications, largely yes, though WFS remains widely deployed. QGIS reads both, and scripts that snapshot to GeoPackage do not care which one fed them.",[14,1794,1795,1798],{},[183,1796,1797],{},"Can I edit features through OGC API Features?","\nWhere the server implements Part 4 and QGIS supports it, yes. Most public servers are read-only.",[14,1800,1801,1804,1805,205],{},[183,1802,1803],{},"Does QGIS Server publish OGC API Features?","\nYes. Projects published with QGIS Server expose their vector layers as collections, as covered in ",[26,1806,1808],{"href":1807},"\u002Fpyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002Fpublish-layers-with-qgis-server-pyqgis\u002F","publishing layers with QGIS Server",[14,1810,1811,1814],{},[183,1812,1813],{},"Why fetch JSON myself if the provider does it?","\nDiscovery. The provider needs a collection id; the landing page and collections list are how a script finds one without a human browsing the server.",[172,1816,1818],{"id":1817},"related","Related",[177,1820,1821,1826,1831,1836,1842],{},[180,1822,1823,1825],{},[26,1824,29],{"href":28}," — the guide this recipe belongs to",[180,1827,1828],{},[26,1829,1830],{"href":211},"Load a WFS Layer in PyQGIS",[180,1832,1833],{},[26,1834,1835],{"href":1701},"Load an ArcGIS REST Feature Service in PyQGIS",[180,1837,1838],{},[26,1839,1841],{"href":1840},"\u002Fspatial-data-processing-automation\u002Fweb-services-and-remote-data\u002Fmake-http-requests-with-qgsnetworkaccessmanager-pyqgis\u002F","Make HTTP Requests with QgsNetworkAccessManager in PyQGIS",[180,1843,1844],{},[26,1845,1847],{"href":1846},"\u002Fspatial-data-processing-automation\u002Fcoordinate-reference-systems\u002Ftransform-point-coordinates-pyqgis\u002F","Transform Point Coordinates Between CRS in PyQGIS",[1849,1850,1851],"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 .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}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);}",{"title":226,"searchDepth":243,"depth":243,"links":1853},[1854,1855,1856,1857,1858,1859,1860,1861,1862,1863,1864],{"id":174,"depth":243,"text":175},{"id":215,"depth":243,"text":216},{"id":782,"depth":243,"text":783},{"id":1022,"depth":243,"text":1023},{"id":1234,"depth":243,"text":1235},{"id":1388,"depth":243,"text":1389},{"id":1710,"depth":243,"text":1711},{"id":1728,"depth":243,"text":1729},{"id":1772,"depth":243,"text":1773},{"id":1785,"depth":243,"text":1786},{"id":1817,"depth":243,"text":1818},"Read collections from an OGC API – Features server with the OAPIF provider — discovering collections from the landing page, loading items as layers, restricting requests to the map extent, choosing a CRS, filtering on the server and taking a local snapshot.","md",{"slug":1868,"type":1869,"breadcrumb":1870,"datePublished":1871,"dateModified":1871},"load-ogc-api-features-layer-pyqgis","article","Load an OGC API Features Layer","2026-09-17","\u002Fspatial-data-processing-automation\u002Fweb-services-and-remote-data\u002Fload-ogc-api-features-layer-pyqgis",{"title":5,"description":1865},"spatial-data-processing-automation\u002Fweb-services-and-remote-data\u002Fload-ogc-api-features-layer-pyqgis\u002Findex","ouSdaL61Q1M9iR-MtKLiruIKX6MKkWL8zDjf3Vbj6Fk",1789632908656]