4

When I try to load many huge rasterlayer into QGIS it blocks the UI. So I try to implement a QGIS-batch-raster-loading task in order to keep QGIS responsive.

Calling the task itself from within my plugin works so far but I cannot add layers to my project like this:

project = QgsProject.instance()
project.addMapLayers([my_array])

In the QGIS docs this warning is written:

Any background task (regardless of how it is created) must NEVER use any QObject that lives on the main thread, such as accessing QgsVectorLayer, QgsProject or perform any GUI based operations like creating new widgets or interacting with existing widgets. Qt widgets must only be accessed or modified from the main thread. Data that is used in a task must be copied before the task is started. Attempting to use them from background threads will result in crashes.

I wonder if this means that it is just not possible to bulk add layers from within a QgsTask or if I miss something?

The main snippet of my code is:

class RandomIntegerSumTask(QgsTask):
    """This shows how to subclass QgsTask"""

    def __init__(self, description, duration, layer):
        super().__init__(description, QgsTask.CanCancel)
        self.duration = duration
        self.total = 0
        self.iterations = 0
        self.exception = None
        self.layer=layer

    def addRasterLayer(self,layers_to_add, project):
        layerarray=[]
        mycrs = QgsCoordinateReferenceSystem.fromEpsgId(25832)
        for path,name in layers_to_add.items():
            print (path, name)
            rlayer = QgsRasterLayer(path, name)
            if rlayer is None:  
                continue
            rlayer.setCrs(mycrs,True)
            layerarray.append(rlayer)

            project.addMapLayers([rlayer])

    def run(self):
        """Here you implement your heavy lifting.
        Should periodically test for isCanceled() to gracefully
        abort.
        This method MUST return True or False.
        Raising exceptions will crash QGIS, so we handle them
        internally and raise them in self.finished
        """
        try:
            project = QgsProject.instance()
            self.layers_to_add={}
            for path in self.layer:
                name = QFileInfo(path).baseName()
                self.layers_to_add[path]=name

            self.addrasterlayer(self.layers_to_add,project)

1 Answer 1

5

Ok. I figured it out. I am not sure if this is the best practice but it works.

First of all I use a pyqtSignal:

class BatchLoadRasters(QgsTask):
    """This shows how to subclass QgsTask"""
    progress= QtCore.pyqtSignal(bool, object)  # For reporting progress

Then I move the layer to the main thread and emmit this signal when the layer is created within the task and hand over the layer:

def addrasterlayer(self,layers_to_add, project):
    mycrs = QgsCoordinateReferenceSystem.fromEpsgId(25832)
    for path,name in layers_to_add.items():
        rlayer = QgsRasterLayer(path, name)
        if rlayer is None:
            continue
        rlayer.setCrs(mycrs,True)
        rlayer.moveToThread(QCoreApplication.instance().thread())
        self.progress.emit(True, rlayer)

Outside of my QgsTask I connect to the signal and add the layer to the project: batchload = BatchLoadRaster('test', layer_to_add) batchload.progress.connect(self.progress)

def progress(self,status, layer):
    self.project.addMapLayer(layer,True)

I found a hint from Nyall Dawson which was very helpful: http://osgeo-org.1560.x6.nabble.com/QGIS-Developer-Any-gotchas-related-to-working-with-QgsVectorLayer-inside-a-QgsTask-td5397166.html

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.