3

I'm developping a Python standalone app that needs a dynamic map (with the zoom and pan tools).

I chose to use PyQGIS that allows me to work with QGIS projects. I managed to create a class that takes a path to a project, loads it and displays it. However, by default, the largest layer is shown which may be a problem.

My QGIS project (that can vary, it depends on the needs) has the world's map as its largest layer with some smaller layers, for example a specific town. I want to give to my class a layer (throught a name or id) that will be shown by default (the canvas will be zoomed to that layer).

How can I do it ?

I want to add that, obviously, I searched for a solution before asking my question and tried some options with the QgsMapCanvas' methods (zoomToFeatureExtent and the more simple method zoomIn). But I can't make it work. I don't get any error whatsoever. If I check the extent of the canvas, it's well set (the same as the layer i chose) but I don't have the wanted result on the screen. I don't know what I'm doing wrong.

Here's the code I have that show my QGIS project in a canvas (I deleted the part where i add the zoom and pan tool to make it less long, I don't think it's needed) :

class MyWindow(QMainWindow):
    def __init__(self,path):
        QMainWindow.__init__(self)
        self.canvas = QgsMapCanvas()
        self.canvas.setCanvasColor(Qt.white)

        self.project = QgsProject.instance()
        self.bridge = QgsLayerTreeMapCanvasBridge(self.project.layerTreeRoot(), self.canvas)
        self.project.read(path)

        self.canvas.freeze(True)
        self.canvas.refresh()
        self.canvas.freeze(False)
        self.canvas.repaint()
        self.setCentralWidget(self.canvas)
4
  • How did you use zoomToFeatureExtent() ?
    – JULESG
    Jul 6 at 8:19
  • @JULESG I don't have the specific code anymore so please don't mind the potential syntax errors. It was something like this : layer = QgsProject.instance().mapLayersByName('name')[0] self.canvas.zoomToFeatureExtent(layer.extent()) I might use it the wrong way though. Jul 6 at 9:47
  • Instead of self.project = QgsProject.instance() try to use self.project = QgsProject() and then replace every QgsProject.instance() by self.project That's what I did in my plugin and it seems to work
    – JULESG
    Jul 6 at 10:10
  • @JULESG It doesn't really change anything, I now have an exit code different from 0 but that's it (-1073741819). I changed self.project and used it in the line just as you said. Jul 6 at 10:50
2

I find that the QgsLayerTreeMapCanvasBridge is causing some trouble for zooming on a specific layer because it updates the canvas automatically.

You have to remove it from your code to make it work, here is a snippet that allows me to zoom on a specific layer, "Layer 3", in a QMainWindow:

class MapViewer(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        self.canvas = QgsMapCanvas()
        self.canvas.setCanvasColor(Qt.white)

        path = "test.qgz"
        self.project = QgsProject().instance()
        self.project.read(path)

        # List the layers in your project and add them to your canvas
        layers = [layer for layer in self.project.mapLayers().values()]
        layers.reverse() # reverse to keep the layer order from the project
        self.canvas.setLayers(layers)

        # Get the layers you want to zoom in by name
        layer = self.project.mapLayersByName("Layer 3")[0]
        self.canvas.zoomToFeatureExtent(layer.extent())

        self.setCentralWidget(self.canvas)

Screenshot of the project test.qgz open in QGIS and open in a QMainWindow with a zoom on "Layer 3" :

Screenshot of the result

2
1

I believe that the proper way to deal with this problem is to add the following line after declaring an instance of QgsLayerTreeMapCanvasBridge.

E.g.

self.bridge = QgsLayerTreeMapCanvasBridge(self.project.layerTreeRoot(), self.canvas)
self.bridge.setAutoSetupOnFirstLayer(False)

You can then use the setExtent() method of the QgsMapCanvas class to set the canvas extent to a layer extent. The advantage of this method over zoomToFeatureExtent() is that it works for both raster and vector layers. If you wish to zoom out slightly from the layer extent you can add a call to zoomByFactor() and pass a float value slightly greater than 1.

Below is a minimal but complete standalone script example. Just change the project path and layer name.

from qgis.core import QgsApplication, QgsProject, QgsRasterLayer
from qgis.gui import QgsMapCanvas, QgsLayerTreeMapCanvasBridge
from PyQt5.QtWidgets import QApplication, QMainWindow


class myWindow(QMainWindow):

    def __init__(self, path, layer_name):
        QMainWindow.__init__(self)
        
        self.path = path
        self.layer_name = layer_name
        
        self.setGeometry(150, 150, 750, 500)
        self.canvas = QgsMapCanvas(self)
        self.setCentralWidget(self.canvas)
        self.project = QgsProject.instance()
        self.bridge = QgsLayerTreeMapCanvasBridge(self.project.layerTreeRoot(), self.canvas)
        self.bridge.setAutoSetupOnFirstLayer(False)
        self.project.read(self.path)
        self.layer = self.project.mapLayersByName(self.layer_name)[0]
        self.canvas.setExtent(self.layer.extent())
        self.canvas.zoomByFactor(1.1)
        self.canvas.refresh()
        

def main():
    app = QApplication([])
    qgs = QgsApplication([], False)
    qgs.setPrefixPath("C:/OSGeo4W64/apps/qgis", True)
    qgs.initQgis()
    
    project_path = 'C:\\Users\\Path\\To\\Your_project.qgs'
    name = 'Layer_name'
    
    w = myWindow(project_path, name)
    w.show()
    sys.exit(app.exec_())
        
if __name__ == "__main__":
    main()

Result:

enter image description here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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