3

I am trying to show selected features of a layer on QDialog within a plugin.

I created the QgsMapCanvas widget and add into QDialog, later set the layers but it does not show any feature.

self.fiberlayer = QgsProject().instance().mapLayersByName("Fiber Cable")[0]
if self.fiberlayer.selectedFeatureCount() > 0:
    vlayer = self.fiberlayer.materialize(QgsFeatureRequest().setFilterFids(
        self.fiberlayer.selectedFeatureIds()))
    vlayer.renderer().symbol().setWidth(1)
    vlayer.renderer().symbol().setColor(QColor("blue"))                
    print(vlayer.isValid())# its true

    self.dlg.map_scene.setDestinationCrs(self.fiberlayer.crs())
    rasterLyr = QgsRasterLayer("type=xyz&url=https://tile.openstreetmap.org/{z}/{x}/{y}.png",
                               "Open Street Map",
                               "wms")
    print(rasterLyr.isValid()) #raster also valid                
    self.dlg.map_scene.setLayers([vlayer, rasterLyr])
    self.dlg.map_scene.setExtent(vlayer.extent(), True)                
    self.dlg.map_scene.refreshAllLayers()

QgsMapCanvas is not showing any layer but if I tried this in QGIS Python console it's giving the result. What is wrong with a plugin?

2 Answers 2

4

TLDR: When running the code inside a separate class, make your layer objects instance attributes (members of self).

I made a few tests to try and reproduce your issue. The results of these are summarized below.

The following simple, procedural code block works works fine when run from the Python console (using one of my own vector layers to test):

dlg = QDialog()
map_scene = QgsMapCanvas(dlg)
layout = QVBoxLayout(dlg)
layout.addWidget(map_scene)
dlg.setLayout(layout)
fiberlayer = QgsProject().instance().mapLayersByName("AUS_roads")[0]
if fiberlayer.selectedFeatureCount() > 0:
    vlayer = fiberlayer.materialize(QgsFeatureRequest().setFilterFids(
        fiberlayer.selectedFeatureIds()))
    vlayer.renderer().symbol().setWidth(1)
    vlayer.renderer().symbol().setColor(QColor("blue"))
    print(vlayer.isValid())# its true

    map_scene.setDestinationCrs(fiberlayer.crs())
    rasterLyr = QgsRasterLayer("type=xyz&url=https://tile.openstreetmap.org/{z}/{x}/{y}.png",
                               "Open Street Map",
                               "wms")
    print(rasterLyr.isValid()) #raster also valid
    map_scene.setLayers([vlayer, rasterLyr])
    map_scene.setExtent(vlayer.extent(), True)
    map_scene.refreshAllLayers()
    
dlg.setGeometry(250, 250, 700, 500)
dlg.show()

Not knowing exactly how your plugin is structured, I tried the following where a QgsMapCanvas object is created in a class which inherits from QDialog, and is instantiated inside another class which is in turn instantiated inside a running QGIS instance. In this example, the vector and raster objects are not created as instance attributes with the self keyword (as is shown in your question). The result is that the dialog is shown but the layers are not displayed on the canvas.

class testClass():
    def __init__(self):
        self.dlg = myDialog()
        self.fiberlayer = QgsProject().instance().mapLayersByName("AUS_roads")[0]
        if self.fiberlayer.selectedFeatureCount() > 0:
            vlayer = self.fiberlayer.materialize(QgsFeatureRequest().setFilterFids(
                self.fiberlayer.selectedFeatureIds()))
            vlayer.renderer().symbol().setWidth(1)
            vlayer.renderer().symbol().setColor(QColor("blue")) 
            print(vlayer.isValid())# its true

            self.dlg.map_scene.setDestinationCrs(self.fiberlayer.crs())
            rasterLyr = QgsRasterLayer("type=xyz&url=https://tile.openstreetmap.org/{z}/{x}/{y}.png",
                                       "Open Street Map",
                                       "wms")
            print(rasterLyr.isValid()) #raster also valid
            self.dlg.map_scene.setLayers([vlayer, rasterLyr])
            self.dlg.map_scene.setExtent(vlayer.extent(), True)
            self.dlg.map_scene.refreshAllLayers()
            self.dlg.show()
    
class myDialog(QDialog):
    def __init__(self):
        QDialog.__init__(self)
        self.setGeometry(250, 250, 700, 500)
        self.map_scene = QgsMapCanvas(self)
        self.layout = QVBoxLayout(self)
        self.layout.addWidget(self.map_scene)
        self.setLayout(self.layout)
        
t = testClass()

Result:

enter image description here

I found that by simply making the layer objects instance attributes with the self keyword, solved the problem e.g.

self.vlayer = self.fiberlayer.materialize(QgsFeatureRequest().setFilterFids(
    self.fiberlayer.selectedFeatureIds()))
    
self.rasterLyr = QgsRasterLayer("type=xyz&url=https://tile.openstreetmap.org/{z}/{x}/{y}.png",
                       "Open Street Map",
                       "wms")
                       
self.dlg.map_scene.setLayers([self.vlayer, self.rasterLyr])

Interestingly enough, I also found that simply adding the layer objects to an instance attribute list, then passing that list to the setLayers() method also worked fine.

See below for working code snippet and result. Just replace with your own vector layer name to test.

class testClass():
    def __init__(self):
        self.dlg = myDialog()
        self.fiberlayer = QgsProject().instance().mapLayersByName("AUS_roads")[0]
        self.layers = list()
        if self.fiberlayer.selectedFeatureCount() > 0:
            vlayer = self.fiberlayer.materialize(QgsFeatureRequest().setFilterFids(
                self.fiberlayer.selectedFeatureIds()))
            vlayer.renderer().symbol().setWidth(1)
            vlayer.renderer().symbol().setColor(QColor("blue")) 
            print(vlayer.isValid())# its true

            self.dlg.map_scene.setDestinationCrs(self.fiberlayer.crs())
            rasterLyr = QgsRasterLayer("type=xyz&url=https://tile.openstreetmap.org/{z}/{x}/{y}.png",
                                       "Open Street Map",
                                       "wms")
            print(rasterLyr.isValid()) #raster also valid
            self.layers.extend([vlayer, rasterLyr])
            self.dlg.map_scene.setLayers(self.layers)
            self.dlg.map_scene.setExtent(vlayer.extent(), True)
            self.dlg.map_scene.refreshAllLayers()
            self.dlg.show()
    
class myDialog(QDialog):
    def __init__(self):
        QDialog.__init__(self)
        self.setGeometry(250, 250, 700, 500)
        self.map_scene = QgsMapCanvas(self)
        self.layout = QVBoxLayout(self)
        self.layout.addWidget(self.map_scene)
        self.setLayout(self.layout)
        
t = testClass()

Result:

enter image description here

2

I think you miss some linking between your QgsMapCanvas and a QgsProject / QgsLayerTree rsp. This link ist created with a QgsLayerTreeMapCanvasBridge.

You can do something like

dlg = QDialog()        # like yours
layout = QHBoxLayout() # layout inside your dialog
map_scene = QgsMapCanvas()
proj = QgsProject()
bridge = QgsLayerTreeMapCanvasBridge(proj.layerTreeRoot(), map_scene) #creates the link
layout.addWidget(map_scene)
rasterLyr = QgsRasterLayer("type=xyz&url=https://tile.openstreetmap.org/{z}/{x}/{y}.png", "Open Street Map", "wms") #example layer
dlg.setLayout(layout)  # set the layout and...
dlg.show()             # ...show the dialog 
proj.addMapLayer(rasterLyr)

Note that you can add layers to the project using its addMapLayer(s) method(s).

This helped me figure this out:

Accessing iface PyQGIS API from Standalone Python script (outside QGIS environment)?

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.