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)
2 Answers 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"
:
-
Thank you so much ! It works now (still need to correct a few things but at least it's zoomed). I really appreciate it. Do you mind sharing the source where you found the information ?PythonJuniorDev– PythonJuniorDev2021年07月06日 16:22:58 +00:00Commented Jul 6, 2021 at 16:22
-
At first I supposed the
QgsLayerTreeMapCanvasBridge
was mandatory because of this :docs.qgis.org/3.16/en/docs/pyqgis_developer_cookbook/… but then I found this page wich doesn't use it : docs.qgis.org/3.16/en/docs/pyqgis_developer_cookbook/… Finally if you look at the description of the class it describe the fact that it update the canvas : qgis.org/pyqgis/3.16/gui/…JULESG– JULESG2021年07月06日 17:31:11 +00:00Commented Jul 6, 2021 at 17:31
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:
zoomToFeatureExtent()
?layer = QgsProject.instance().mapLayersByName('name')[0] self.canvas.zoomToFeatureExtent(layer.extent())
I might use it the wrong way though.self.project = QgsProject.instance()
try to useself.project = QgsProject()
and then replace everyQgsProject.instance()
byself.project
That's what I did in my plugin and it seems to work