每隔约 5 秒/之后修改 QGIS 图层(不阻塞主线程)

问题描述 投票:0回答:1

我为 QGIS 3.36.2(使用 Python 3.12.3)编写了一个 Python 脚本,它执行以下操作:

  1. 创建图层
  2. 启动 HTTP GET 请求以获取新坐标(默认情况下异步运行)
  3. 使用这些坐标在图层上绘制标记(旧标记首先被删除,必须在主线程上运行afaik)

步骤 1 仅发生一次。 2. + 3. 应无限期运行,但如果出现错误或用户停止脚本,则停止。为了测试我只想运行它,例如10次。

到目前为止我发现/尝试过的:

  • time.sleep()
    (按照这里的建议)完全冻结了QGIS。
  • sched
    调度程序(参见下面的代码)也会阻塞主线程并冻结QGIS。
  • threading.Timer
    每次都会启动一个新线程(并且您将无法停止循环),因此 answer 建议不要使用它 - 因此未经测试。
  • 我无法使用
    Tkinter
    ,因为QGIS的python不支持。
  • asyncio
    (如here建议)似乎在这个QGIS版本中也没有得到完全支持(尝试运行this示例时出现很多错误,但它在Python 3.9控制台中工作正常)而且它也很友善阻塞,因为它使用协程(参见this问题;你可以
    yield
    )。

如果没有错误,如何重复步骤 2 和 3 多次,例如最后一次迭代完成后 5 秒,不会使用某种类型的

sleep
阻塞 GUI(尤其是地图查看器),并且最好不使用任何额外的库?

我的代码:

#imports here
class ArrowDrawerClass:
    layer = None
    dataprovider = None
    feature = None
    repeat = True
    url = "someURL"
    repeatCounter = 0
    myscheduler = sched.scheduler(time.time,time.sleep)
    
    def __init__(self):
        self.createNewLayer()
    
    def createNewLayer(self):
        layername = "ArrowLayer"
        self.layer =  QgsVectorLayer('Point', layername, "memory")
        self.dataprovider = self.layer.dataProvider()
        self.feature = QgsFeature()
        #Set symbol, color,... of layer here
        QgsProject.instance().addMapLayers([self.layer])

    def doRequest(self):
        request = QNetworkRequest(QUrl(self.url))
        request.setTransferTimeout(10000) #10s
        self.manager = QNetworkAccessManager()
        self.manager.finished.connect(self.handleResponse)
        self.manager.get(request)

    def handleResponse(self, reply):
        err = reply.error()

        if err == QtNetwork.QNetworkReply.NetworkError.NoError:
            bytes = reply.readAll()
            replytext = str(bytes, 'utf-8').strip()
            #extract coordinates here ...
            self.drawArrow(x,y)
        else:
            self.displayError(str(err),reply.errorString())

    def drawArrow(self,x,y):
        self.layer.dataProvider().truncate() #removes old marker
        point1 = QgsPointXY(x,y)
        self.feature.setGeometry(QgsGeometry.fromPointXY(point1))
        self.dataprovider.addFeatures([self.feature])
        self.layer.updateExtents()
        self.layer.triggerRepaint()
        self.repeatCounter += 1
        self.repeatEverything()

    def displayError(self,code,msg):
        self.repeat = False
        #show error dialog here

    def start(self):
        self.myscheduler.enter(0,0,self.doRequest)
        self.myscheduler.run()

    def repeatEverything(self):
        print("counter:",self.repeatCounter)
        if self.repeat and self.repeatCounter<10:
            print("repeat")
            self.myscheduler.enter(5,0,self.test) #TODO: Call "self.doRequest()" instead
            self.myscheduler.run()
        else:
            print("don't repeat!")

    def test(self):
        print("test!")

adc = ArrowDrawerClass()
adc.start()
python qgis
1个回答
0
投票

我设法通过“单次射击”(仅触发一次)来完成此任务

QTimer

from PyQt5.QtCore import QTimer
#Other imports here

class ArrowDrawerClass:
    #Declare variables here

    def __init__(self):
        self.timer = QTimer()
        self.timer.setSingleShot(True)
        self.timer.timeout.connect(self.doRequest)
        self.createNewLayer()
    
    #def createNewLayer(self): #No changes
    #def doRequest(self): #No changes
    #def handleResponse(self, reply): #No changes
    
    def drawArrow():
        #draw arrow here
        self.repeatCounter += 1
        self.repeatEverything()
    
    def displayError(self,code,msg):
        self.stopTimer()
        self.repeat = False
        #show error dialog here
    
    def repeatEverything(self):
        print("counter:",self.repeatCounter)
        #print("Main Thread:",(isinstance(threading.current_thread(), threading._MainThread)))
        if self.repeat and self.repeatCounter<10:
            self.startTimer()
        else:
            self.stopTimer()
    
    def startTimer(self):
        if not self.timer.isActive():
            self.timer.start(5000) #5s
    
    def stopTimer(self):
        if self.timer.isActive():
            self.timer.stop()

adc = ArrowDrawerClass()
adc.doRequest() #Call the function directly, so there's no 5s delay at the beginning

这不会阻塞 UI 或冻结 QGIS(除了由

truncate()
引起的小冻结,但这是一个不同的问题)。

根据docs

QTimer
使用事件循环,并且
repeatEverything
中的第二次打印在我的测试中始终输出
True
,因此无需担心更新UI。

© www.soinside.com 2019 - 2024. All rights reserved.