4

I struggle with accessing a WFS (Cyclomedia Atlas Recordings) with the following code from a PyQGIS plugin:

import urllib.parse
from qgis.core import QgsVectorLayer, QgsDataSourceUri

wfs_params = {'service': 'WFS',
              'version': '1.1.0',
              'request': 'GetFeature',
              'typename': 'atlas:Recording',
              'srsname': 'EPSG:25832'}
uri = QgsDataSourceUri()
uri.setEncodedUri('https://atlas.cyclomedia.com/recordings/wfs?' + urllib.parse.unquote(urllib.parse.urlencode(wfs_params)))
uri.setUsername('me')
uri.setPassword('my_password')
cyclom_photos_lyr = QgsVectorLayer(uri.uri(), 'Cyclorama Locations', 'WFS')

The relevant parts of the WFS' capabilities response look like this (it's password protected):

<ows:Operation name="GetFeature">
  <ows:DCP>
    <ows:HTTP>
      <ows:Get xlink:href="https://atlas.cyclomedia.com/recordings/wfs"/>  
      <ows:Post xlink:href="https://atlas.cyclomedia.com/recording/wfs"/>
    </ows:HTTP></ows:DCP><ows:Parameter name="resultType">
...
<wfs:FeatureTypeList>
  <wfs:Operations>
    <wfs:Operation>
      Query
    </wfs:Operation>
  </wfs:Operations><!--
<wfs:FeatureType>
  <wfs:Name>atlas:Recording</wfs:Name>
  <wfs:Title>Recording</wfs:Title>
  <wfs:DefaultSRS>EPSG:4326</wfs:DefaultSRS><!--NAD83-->
  ...
  <wfs:OtherSRS>EPSG:25832</wfs:OtherSRS><!--ETRS89-->
  ...

I am not using the uri directly as described in the documentation (https://docs.qgis.org/3.4/en/docs/pyqgis_developer_cookbook/loadlayer.html) because I'm not aware of passing credentials to the service directly via uri, therefore the QgsDataSourceUri().setUsername()... construct.

The above code gives me the following error:

2019-10-08T12:08:54     WARNING    GetCapabilities failed for url user='me' password='my_password' https://atlas.cyclomedia.com/recordings/wfs?service='WFS' request='GetFeature' srsname='EPSG:25832' typename='atlas:Recordings' version='1.1.0' table="" sql=: unexpected end of file
2019-10-08T12:08:54     WARNING    Download of capabilities failed: Protocol "" is unknown

What am I doing wrong or what am I probably missing?

The WFS is accessible via Data Source Manager without any problem (dots in the screenshot below), but I need to add it programmatically for convenience reasons.

enter image description here

1
  • If you load the layer via data source manager you can check the connect string under Layer Properties->Information. Mybe something is different to the return of uri.uri()? Oct 8, 2019 at 12:50

2 Answers 2

6

I struggled with a similar issue myself and the solution was to use the setParam method of the QgsDataSourceUri object to set the query params.

uri = QgsDataSourceUri()
uri.setParam('service', 'wfs')
uri.setParam('version', '1.1.0')
uri.setParam('request', 'GetFeature')
uri.setParam('typename', 'atlas:Recording')
uri.setParam('srsName', 'EPSG:25832')
uri.setUsername('me')
uri.setPassword('my_password')
uri.setParam('url', 'https://atlas.cyclomedia.com/recordings/wfs')

layer = QgsVectorLayer(uri.uri(), 'Cyclorama Locations', 'WFS')
assert layer.isValid()
3
  • This doesn't work for me in QGIS 3.22. The resulting layer is invalid although the server works fine when checked within a browser. I didn't set username and password since I don't have those, but I believe that's not the reason. The layer would be valid then, only that QGIS wouldn't be able to load the data. Same issue with the Sentinel Hub WFS, for example. Could you check if it works for you in 3.20 or 3.22? Dec 14, 2021 at 9:12
  • I haven't tried this (atlas.cyclomedia.com) myself, I just used it in the answer as an example service. Could you give me the url of your WFS so that I could try with it?
    – Joonala
    Dec 20, 2021 at 12:00
  • I think I've figured it out, see my answer below Dec 21, 2021 at 6:54
2

Joonala's answer gives the right clue, but you have to define a BBOX for the request, or QGIS won't know which area to request features for. It may not be obvious because when you add a WFS via the QGIS GUI, it doesn't ask you to specify one. But if you go to the layer's info, you'll see that it added restrictToRequestBBOX='1' to the layer's list of parameters. It means that the BBOX will be calculated on the fly every time you pan or zoom. So when creating a WFS layer via PyQGIS, you have to either do:

uri.setParam('restrictToRequestBBOX', '1')
or
uri.setParam('BBOX', <your BBOX>)

The docs on QgsDataSourceUri aren't very useful in the case of WFS. They don't describe which parameters can be passed to setParam(). I first thought it would accept arbitrary params, but in fact, it won't add to the resulting URL any parameters it doesn't understand. One specifically tricky case is that it won't understand typenames (plural). It must be typename (singular). Depending on the WFS version, it will then automatically pluralize it in the resulting URL.

To pass arbitrary parameters, add them to the url's query string after the '?'. Finally, you don't have to specify the service and request parameters, - they'll be added automatically. So a working example would be:

uri = QgsDataSourceUri()
uri.setParam('typename', 'DigitalGlobe:FinishedFeature')
uri.setParam('srsName', 'EPSG:4326')
uri.setUsername('<username>')
uri.setPassword('<password>')
uri.setParam('url','https://securewatch.digitalglobe.com/catalogservice/wfsaccess?connectid=<my_connect_id>')

layer = QgsVectorLayer(uri.uri(), 'my wfs', 'wfs')
assert layer.isValid()
1
  • Thank you! This is doubly confusing as qgis.org/pyqgis/master/core/… says restrictToRequestBBOX=1 which suggests that it is 1 by default. I guess it does not mean that in that particular place in the docs. Sep 8 at 8:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.