2

I'm creating a small QGIS3.16 plugin with PyQGIS. I'd want to add a vector (PostGIS) layer to QGIS, and immediately filter it with the QGIS canvas extent.

I found the QgsFeatureRequest(rectangle:QgsRectangle) class. I want to use it with "self.iface.canvas.extent()".

How do I use this class to filter the layer I just added to QGIS?

This is my code :

# Collect the canvas extent
extent = self.iface.mapCanvas().extent()
request = QgsFeatureRequest()
request.setFilterRect(extent)

# Add the vector layer to QGIS
self.uri = QgsDataSourceUri()
self.uri.setConnection("adress", "port", "database", '', '', False,'idauth')
self.uri.setDataSource("schema", tablename, "geomfield", None , pkey)    
self.ctrs_cibles=QgsVectorLayer(self.uri.uri(), "layername", "postgres")
root = QgsProject.instance().layerTreeRoot()
if self.ctrs_cibles.featureCount()>0:
   QgsProject.instance().addMapLayer(self.ctrs_cibles, False)
   root.insertLayer(0, self.ctrs_cibles)
       
  • I tried to insert "request" as the 4th parameter of the setDataSource method : self.uri.setDataSource("schema", tablename, "geomfield", request , pkey) But pyQgis waits for a string, not a qgsFeatureRequest.

  • I've got the same result trying to use setSubsetString. The following command won't work : it needs "request" to be a plain text query.

    self.ctrs_cibles.setSubsetString(request)
  • I tried to use a getFeatures() method :
for feature in self.ctrs_cibles.getFeatures(request):
    do something...

This method needs a qgsFeatureRequest but it just creates an iterator, it doesn't filter the layer. I could use the iterator to create another layer and add the filtered features inside, but I am looking for a more straightforward way to filter a layer with the canvas extent.

3 Answers 3

5

You can create a temporary layer based on your Postgis layer.

You can either filter features of the layer wich intersect the canvas extent, or filter the features wich are completly within the canvas extent.

On the example below, I have a layer (red), a canvas extent (red line) zoomed on the green feature, features intersecting the extent (pink features) and features within the extent (purple features).

Example

To create those 2 layers I used this code :

import tempfile
tf = tempfile.TemporaryDirectory()

# IMPORT YOUR LAYER

if self.ctrs_cibles.featureCount() > 0:
    # Creation of the layer with the features wich intersect the extent
    self.ctrs_cibles.selectByRect(extent)
    writer = QgsVectorFileWriter.writeAsVectorFormat(self.ctrs_cibles, tf.name +'/'+'filtered_layer_intersect.shp', "utf-8", self.ctrs_cibles.crs(), "ESRI Shapefile", onlySelected=True)
    iface.addVectorLayer( tf.name +'/'+'filtered_layer_intersect.shp', '', 'ogr')
    # Creation of the layer with the features wich are within the extent
    inside_extent_ids = []
    for feature in self.ctrs_cibles.getSelectedFeatures():
        if feature.geometry().within(QgsGeometry().fromRect(extent)):
            inside_extent_ids.append(feature.id())
    self.ctrs_cibles.selectByIds(inside_extent_ids)
    writer = QgsVectorFileWriter.writeAsVectorFormat(self.ctrs_cibles, tf.name +'/'+'filtered_layer_within.shp', "utf-8", self.ctrs_cibles.crs(), "ESRI Shapefile", onlySelected=True)
    iface.addVectorLayer( tf.name +'/'+'filtered_layer_within.shp', '', 'ogr')
0
4

This might not be what you need, but do you know of

PostgreSQL execute and load SQL?

This algorithm performs a SQL database query on a PostGIS database connected to QGIS and loads the query results as a new layer.

e = iface.mapCanvas().extent()

xmin = e.xMinimum()
xmax = e.xMaximum()
ymin = e.yMinimum()
ymax = e.yMaximum()
sqlquery = 'select * from public.ok_bs_riks t1 where st_intersects(t1.wkb_geometry, st_makeenvelope({0}, {1}, {2}, {3}, 3006))'.format(xmin, ymin, xmax, ymax)

processing.runAndLoadResults("qgis:postgisexecuteandloadsql", 
    {'DATABASE':'lmv',
    'SQL':sqlquery,'ID_FIELD':'id','GEOMETRY_FIELD':'wkb_geometry'})

enter image description here

1
  • 1
    No, I didn't know, thanks. It opens a lot opportunities to manage data and queries with PostgreSQL/PostGIS instaed of Python, which seems a more flexible way to code.
    – V Damoy
    Commented Jan 5, 2022 at 10:14
2

Finally I found a third way to filter my vector layer, without creating a shapefile : I use the "materialize" method from the "qgsVectorLayer"class, to create a memory layer which contains only the query result (the query here returns shapes which intersect the map canvas).

self.ctrs_cibles=QgsVectorLayer(self.uri.uri(), "contours", "postgres")
extent = self.iface.mapCanvas().extent()
self.ctrs_cible_canvas = self.ctrs_cibles.materialize(QgsFeatureRequest().setFilterRect(extent))

Then I insert the memory layer "self.ctrs_cible_canvas" at the top of the map legend :

QgsProject.instance().addMapLayer(self.ctrs_cible_canvas, False)
root = QgsProject.instance().layerTreeRoot()
root.insertLayer(0, self.ctrs_cible_canvas)

At last, I use the filtered memory layer, and I destruct it when I don't need it anymore.

Hope this will help...

Your Answer

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

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