class FileGPSService(GPSService): def __init__(self, filename): super(FileGPSService, self).__init__() self.file = None self.timer = QTimer() self.timer.setInterval(100) self.timer.timeout.connect(self._read_from_file) self.filename = filename def connectGPS(self, portname): # Normally the portname is passed but we will take a filename # because it's a fake GPS service if not self.isConnected: self.file = open(self.filename, "r") self.timer.start() self.isConnected = True def _read_from_file(self): line = self.file.readline() if not line: self.file.seek(0) line = self.file.readline() self.parse_data(line) def disconnectGPS(self): if self.file: self.file.close() self.timer.stop() self.file = None self.gpsdisconnected.emit()
def test_link_feature(self): """ Check if an existing feature can be linked """ wrapper = self.createWrapper(self.vl_a, '"name"=\'Douglas Adams\'') # NOQA f = QgsFeature(self.vl_b.fields()) f.setAttributes([self.vl_b.dataProvider().defaultValueClause(0), 'The Hitchhiker\'s Guide to the Galaxy']) self.vl_b.addFeature(f) def choose_linked_feature(): dlg = QApplication.activeModalWidget() dlg.setSelectedFeatures([f.id()]) dlg.accept() btn = self.widget.findChild(QToolButton, 'mLinkFeatureButton') timer = QTimer() timer.setSingleShot(True) timer.setInterval(0) # will run in the event loop as soon as it's processed when the dialog is opened timer.timeout.connect(choose_linked_feature) timer.start() btn.click() # magically the above code selects the feature here... link_feature = next(self.vl_link.getFeatures(QgsFeatureRequest().setFilterExpression('"fk_book"={}'.format(f[0])))) self.assertIsNotNone(link_feature[0]) self.assertEqual(self.table_view.model().rowCount(), 1)
def _check_path_exists(self, path, text_box): # Validates if the specified folder exists dir = QDir() if not dir.exists(path): msg = self.tr("'{0}' directory does not exist.".format(path)) self.notif_bar.insertErrorNotification(msg) # Highlight textbox control text_box.setStyleSheet(INVALIDATESTYLESHEET) timer = QTimer(self) # Sync interval with that of the notification bar timer.setInterval(self.notif_bar.interval) timer.setSingleShot(True) # Remove previous connected slots (if any) receivers = timer.receivers(QTimer.timeout) if receivers > 0: self._timer.timeout.disconnect() timer.start() timer.timeout.connect(lambda: self._restore_stylesheet( text_box) ) return False return True
def render(self): """ do the rendering. This function is called in the worker thread """ debug("[WORKER THREAD] Calling request() asynchronously", 3) QMetaObject.invokeMethod(self.controller, "request") # setup a timer that checks whether the rendering has not been stopped # in the meanwhile timer = QTimer() timer.setInterval(50) timer.timeout.connect(self.onTimeout) timer.start() debug("[WORKER THREAD] Waiting for the async request to complete", 3) self.loop = QEventLoop() self.controller.finished.connect(self.loop.exit) self.loop.exec_() debug("[WORKER THREAD] Async request finished", 3) painter = self.context.painter() painter.drawImage(0, 0, self.controller.img) return True
class OpenLayersOverviewWidget(QWidget, Ui_Form): def __init__(self, iface, dockwidget, olLayerTypeRegistry): QWidget.__init__(self) Ui_Form.__init__(self) self.setupUi(self) self.__canvas = iface.mapCanvas() self.__dockwidget = dockwidget self.__olLayerTypeRegistry = olLayerTypeRegistry self.__initLayerOL = False self.__fileNameImg = '' self.__srsOL = QgsCoordinateReferenceSystem( 3857, QgsCoordinateReferenceSystem.EpsgCrsId) self.__marker = MarkerCursor(self.__canvas, self.__srsOL) self.__manager = None # Need persist for PROXY bindogr.initOgr() self.__init() def __init(self): self.checkBoxHideCross.setEnabled(False) self.__populateTypeMapGUI() self.__populateButtonBox() self.__registerObjJS() self.lbStatusRead.setVisible(False) self.__setConnections() self.__timerMapReady = QTimer() self.__timerMapReady.setSingleShot(True) self.__timerMapReady.setInterval(20) self.__timerMapReady.timeout.connect(self.__checkMapReady) def __del__(self): self.__marker.reset() # Disconnect Canvas # Canvas QgsMapCanvas.extentsChanged.disconnect(self.__canvas) # Doc WidgetparentWidget QDockWidget.visibilityChanged.disconnect(self.__dockwidget) def __populateButtonBox(self): pathPlugin = "%s%s%%s" % (os.path.dirname(__file__), os.path.sep) self.pbRefresh.setIcon(QIcon(pathPlugin % "mActionDraw.png")) self.pbRefresh.setEnabled(False) self.pbAddRaster.setIcon(QIcon(pathPlugin % "mActionAddRasterLayer.png")) self.pbAddRaster.setEnabled(False) self.pbCopyKml.setIcon(QIcon(pathPlugin % "kml.png")) self.pbCopyKml.setEnabled(False) self.pbSaveImg.setIcon(QIcon(pathPlugin % "mActionSaveMapAsImage.png")) self.pbSaveImg.setEnabled(False) def __populateTypeMapGUI(self): pathPlugin = "%s%s%%s" % (os.path.dirname(__file__), os.path.sep) totalLayers = len(self.__olLayerTypeRegistry.types()) for id in range(totalLayers): layer = self.__olLayerTypeRegistry.getById(id) name = str(layer.displayName) icon = QIcon(pathPlugin % layer.groupIcon) self.comboBoxTypeMap.addItem(icon, name, id) def __setConnections(self): # Check Box self.checkBoxEnableMap.stateChanged.connect( self.__signal_checkBoxEnableMap_stateChanged) self.checkBoxHideCross.stateChanged.connect( self.__signal_checkBoxHideCross_stateChanged) # comboBoxTypeMap self.comboBoxTypeMap.currentIndexChanged.connect( self.__signal_comboBoxTypeMap_currentIndexChanged) # Canvas self.__canvas.extentsChanged.connect( self.__signal_canvas_extentsChanged) # Doc WidgetparentWidget self.__dockwidget.visibilityChanged.connect( self.__signal_DocWidget_visibilityChanged) # WebView Map self.webViewMap.page().mainFrame().javaScriptWindowObjectCleared.connect( self.__registerObjJS) # Push Button self.pbRefresh.clicked.connect( self.__signal_pbRefresh_clicked) self.pbAddRaster.clicked.connect( self.__signal_pbAddRaster_clicked) self.pbCopyKml.clicked.connect( self.__signal_pbCopyKml_clicked) self.pbSaveImg.clicked.connect( self.__signal_pbSaveImg_clicked) def __registerObjJS(self): self.webViewMap.page().mainFrame().addToJavaScriptWindowObject( "MarkerCursorQGis", self.__marker) def __signal_checkBoxEnableMap_stateChanged(self, state): enable = False if state == Qt.Unchecked: self.__marker.reset() else: if self.__canvas.layerCount() == 0: QMessageBox.warning(self, QApplication.translate( "OpenLayersOverviewWidget", "OpenLayers Overview"), QApplication.translate( "OpenLayersOverviewWidget", "At least one layer in map canvas required")) self.checkBoxEnableMap.setCheckState(Qt.Unchecked) else: enable = True if not self.__initLayerOL: self.__initLayerOL = True self.__setWebViewMap(0) else: self.__refreshMapOL() # GUI if enable: self.lbStatusRead.setVisible(False) self.webViewMap.setVisible(True) else: self.lbStatusRead.setText("") self.lbStatusRead.setVisible(True) self.webViewMap.setVisible(False) self.webViewMap.setEnabled(enable) self.comboBoxTypeMap.setEnabled(enable) self.pbRefresh.setEnabled(enable) self.pbAddRaster.setEnabled(enable) self.pbCopyKml.setEnabled(enable) self.pbSaveImg.setEnabled(enable) self.checkBoxHideCross.setEnabled(enable) def __signal_checkBoxHideCross_stateChanged(self, state): if state == Qt.Checked: self.__marker.reset() self.__marker.setVisible(False) else: self.__marker.setVisible(True) self.__refreshMapOL() def __signal_DocWidget_visibilityChanged(self, visible): if self.__canvas.layerCount() == 0: return self.checkBoxEnableMap.setCheckState(Qt.Unchecked) self.__signal_checkBoxEnableMap_stateChanged(Qt.Unchecked) def __signal_comboBoxTypeMap_currentIndexChanged(self, index): self.__setWebViewMap(index) def __signal_canvas_extentsChanged(self): if self.__canvas.layerCount() == 0 or not self.webViewMap.isVisible(): return if self.checkBoxEnableMap.checkState() == Qt.Checked: self.__refreshMapOL() def __signal_pbRefresh_clicked(self, checked): index = self.comboBoxTypeMap.currentIndex() self.__setWebViewMap(index) def __signal_pbAddRaster_clicked(self, checked): index = self.comboBoxTypeMap.currentIndex() layer = self.__olLayerTypeRegistry.getById(index) QGuiApplication.setOverrideCursor(Qt.WaitCursor) layer.addLayer() QGuiApplication.restoreOverrideCursor() def __signal_pbCopyKml_clicked(self, cheked): # Extent Openlayers action = "map.getExtent().toGeometry().toString();" wkt = self.webViewMap.page().mainFrame().evaluateJavaScript(action) rect = QgsGeometry.fromWkt(wkt).boundingBox() srsGE = QgsCoordinateReferenceSystem( 4326, QgsCoordinateReferenceSystem.EpsgCrsId) coodTrans = QgsCoordinateTransform(self.__srsOL, srsGE, QgsProject.instance()) rect = coodTrans.transform( rect, QgsCoordinateTransform.ForwardTransform) line = QgsGeometry.fromRect(rect).asPolygon()[0] wkt = str(QgsGeometry.fromPolylineXY(line).asWkt()) # Kml proj4 = str(srsGE.toProj4()) kmlLine = bindogr.exportKml(wkt, proj4) kml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"\ "<kml xmlns=\"http://www.opengis.net/kml/2.2\" " \ "xmlns:gx=\"http://www.google.com/kml/ext/2.2\" " \ "xmlns:kml=\"http://www.opengis.net/kml/2.2\" " \ "xmlns:atom=\"http://www.w3.org/2005/Atom\">" \ "<Placemark>" \ "<name>KML from Plugin Openlayers Overview for QGIS</name>" \ "<description>Extent of openlayers map from Plugin Openlayers \ Overview for QGIS</description>"\ "%s" \ "</Placemark></kml>" % kmlLine clipBoard = QApplication.clipboard() clipBoard.setText(kml) def __signal_pbSaveImg_clicked(self, cheked): if type(self.__fileNameImg) == tuple: self.__fileNameImg = self.__fileNameImg[0] fileName = QFileDialog.getSaveFileName(self, QApplication.translate( "OpenLayersOverviewWidget", "Save image"), self.__fileNameImg, QApplication.translate( "OpenLayersOverviewWidget", "Image(*.jpg)")) if not fileName == '': self.__fileNameImg = fileName else: return img = QImage(self.webViewMap.page().mainFrame().contentsSize(), QImage.Format_ARGB32_Premultiplied) imgPainter = QPainter() imgPainter.begin(img) self.webViewMap.page().mainFrame().render(imgPainter) imgPainter.end() img.save(fileName[0], "JPEG") def __signal_webViewMap_loadFinished(self, ok): if ok is False: QMessageBox.warning(self, QApplication.translate( "OpenLayersOverviewWidget", "OpenLayers Overview"), QApplication.translate( "OpenLayersOverviewWidget", "Error loading page!")) else: # wait until OpenLayers map is ready self.__checkMapReady() self.lbStatusRead.setVisible(False) self.webViewMap.setVisible(True) self.webViewMap.page().mainFrame().loadFinished.disconnect( self.__signal_webViewMap_loadFinished) def __setWebViewMap(self, id): layer = self.__olLayerTypeRegistry.getById(id) self.lbStatusRead.setText("Loading " + layer.displayName + " ...") self.lbStatusRead.setVisible(True) self.webViewMap.setVisible(False) self.webViewMap.page().mainFrame().loadFinished.connect( self.__signal_webViewMap_loadFinished) url = layer.html_url() self.webViewMap.page().mainFrame().load(QUrl(url)) def __checkMapReady(self): if self.webViewMap.page().mainFrame().evaluateJavaScript( "map != undefined"): # map ready self.__refreshMapOL() else: # wait for map self.__timerMapReady.start() def __refreshMapOL(self): # catch Exception where lat/long exceed limit of the loaded layer # the Exception name is unknown latlon = None try: latlon = self.__getCenterLongLat2OL() except Exception as e: QgsLogger().warning(e.args[0]) if latlon: action = "map.setCenter(new OpenLayers.LonLat(%f, %f));" % (latlon) self.webViewMap.page().mainFrame().evaluateJavaScript(action) action = "map.zoomToScale(%f);" % self.__canvas.scale() self.webViewMap.page().mainFrame().evaluateJavaScript(action) self.webViewMap.page().mainFrame().evaluateJavaScript( "oloMarker.changeMarker();") def __getCenterLongLat2OL(self): pntCenter = self.__canvas.extent().center() crsCanvas = self.__canvas.mapSettings().destinationCrs() if crsCanvas != self.__srsOL: coodTrans = QgsCoordinateTransform(crsCanvas, self.__srsOL, QgsProject.instance()) pntCenter = coodTrans.transform( pntCenter, QgsCoordinateTransform.ForwardTransform) return tuple([pntCenter.x(), pntCenter.y()])
class OpenlayersController(QObject): """ Helper class that deals with QWebPage. The object lives in GUI thread, its request() slot is asynchronously called from worker thread. See https://github.com/wonder-sk/qgis-mtr-example-plugin for basic example 1. Load Web Page with OpenLayers map 2. Update OL map extend according to QGIS canvas extent """ # signal that reports to the worker thread that the image is ready finished = pyqtSignal() def __init__(self, parent, context, webPage, layerType): QObject.__init__(self, parent) debug("OpenlayersController.__init__", 3) self.context = context self.layerType = layerType self.img = QImage() self.page = webPage self.page.loadFinished.connect(self.pageLoaded) # initial size for map self.page.setViewportSize(QSize(1, 1)) self.timerMapReady = QTimer() self.timerMapReady.setSingleShot(True) self.timerMapReady.setInterval(20) self.timerMapReady.timeout.connect(self.checkMapReady) self.timer = QTimer() self.timer.setInterval(100) self.timer.timeout.connect(self.checkMapUpdate) self.timerMax = QTimer() self.timerMax.setSingleShot(True) # TODO: different timeouts for map types self.timerMax.setInterval(2500) self.timerMax.timeout.connect(self.mapTimeout) @pyqtSlot() def request(self): debug("[GUI THREAD] Processing request", 3) self.cancelled = False if not self.page.loaded: self.init_page() else: self.setup_map() def init_page(self): url = self.layerType.html_url() debug("page file: %s" % url) self.page.mainFrame().load(QUrl(url)) # wait for page to finish loading debug("OpenlayersWorker waiting for page to load", 3) def pageLoaded(self): debug("[GUI THREAD] pageLoaded", 3) if self.cancelled: self.emitErrorImage() return # wait until OpenLayers map is ready self.checkMapReady() def checkMapReady(self): debug("[GUI THREAD] checkMapReady", 3) if self.page.mainFrame().evaluateJavaScript("map != undefined"): # map ready self.page.loaded = True self.setup_map() else: # wait for map self.timerMapReady.start() def setup_map(self): rendererContext = self.context # FIXME: self.mapSettings.outputDpi() self.outputDpi = rendererContext.painter().device().logicalDpiX() debug(" extent: %s" % rendererContext.extent().toString(), 3) debug(" center: %lf, %lf" % (rendererContext.extent().center().x(), rendererContext.extent().center().y()), 3) debug(" size: %d, %d" % ( rendererContext.painter().viewport().size().width(), rendererContext.painter().viewport().size().height()), 3) debug(" logicalDpiX: %d" % rendererContext.painter(). device().logicalDpiX(), 3) debug(" outputDpi: %lf" % self.outputDpi) debug(" mapUnitsPerPixel: %f" % rendererContext.mapToPixel(). mapUnitsPerPixel(), 3) # debug(" rasterScaleFactor: %s" % str(rendererContext. # rasterScaleFactor()), 3) # debug(" outputSize: %d, %d" % (self.iface.mapCanvas().mapRenderer(). # outputSize().width(), # self.iface.mapCanvas().mapRenderer(). # outputSize().height()), 3) # debug(" scale: %lf" % self.iface.mapCanvas().mapRenderer().scale(), # 3) # # if (self.page.lastExtent == rendererContext.extent() # and self.page.lastViewPortSize == rendererContext.painter(). # viewport().size() # and self.page.lastLogicalDpi == rendererContext.painter(). # device().logicalDpiX() # and self.page.lastOutputDpi == self.outputDpi # and self.page.lastMapUnitsPerPixel == rendererContext. # mapToPixel().mapUnitsPerPixel()): # self.renderMap() # self.finished.emit() # return self.targetSize = rendererContext.painter().viewport().size() if rendererContext.painter().device().logicalDpiX() != int(self.outputDpi): # use screen dpi for printing sizeFact = self.outputDpi / 25.4 / rendererContext.mapToPixel().mapUnitsPerPixel() self.targetSize.setWidth( rendererContext.extent().width() * sizeFact) self.targetSize.setHeight( rendererContext.extent().height() * sizeFact) debug(" targetSize: %d, %d" % ( self.targetSize.width(), self.targetSize.height()), 3) # find matching OL resolution qgisRes = rendererContext.extent().width() / self.targetSize.width() olRes = None for res in self.page.resolutions(): if qgisRes >= res: olRes = res break if olRes is None: debug("No matching OL resolution found (QGIS resolution: %f)" % qgisRes) self.emitErrorImage() return # adjust OpenLayers viewport to match QGIS extent olWidth = rendererContext.extent().width() / olRes olHeight = rendererContext.extent().height() / olRes debug(" adjust viewport: %f -> %f: %f x %f" % (qgisRes, olRes, olWidth, olHeight), 3) olSize = QSize(int(olWidth), int(olHeight)) self.page.setViewportSize(olSize) self.page.mainFrame().evaluateJavaScript("map.updateSize();") self.img = QImage(olSize, QImage.Format_ARGB32_Premultiplied) self.page.extent = rendererContext.extent() debug("map.zoomToExtent (%f, %f, %f, %f)" % ( self.page.extent.xMinimum(), self.page.extent.yMinimum(), self.page.extent.xMaximum(), self.page.extent.yMaximum()), 3) self.page.mainFrame().evaluateJavaScript( "map.zoomToExtent(new OpenLayers.Bounds(%f, %f, %f, %f), true);" % (self.page.extent.xMinimum(), self.page.extent.yMinimum(), self.page.extent.xMaximum(), self.page.extent.yMaximum())) olextent = self.page.mainFrame().evaluateJavaScript("map.getExtent();") debug("Resulting OL extent: %s" % olextent, 3) if olextent is None or not hasattr(olextent, '__getitem__'): debug("map.zoomToExtent failed") # map.setCenter and other operations throw "undefined[0]: # TypeError: 'null' is not an object" on first page load # We ignore that and render the initial map with wrong extents # self.emitErrorImage() # return else: reloffset = abs((self.page.extent.yMaximum()-olextent[ "top"])/self.page.extent.xMinimum()) debug("relative offset yMaximum %f" % reloffset, 3) if reloffset > 0.1: debug("OL extent shift failure: %s" % reloffset) self.emitErrorImage() return self.mapFinished = False self.timer.start() self.timerMax.start() def checkMapUpdate(self): if self.layerType.emitsLoadEnd: # wait for OpenLayers to finish loading loadEndOL = self.page.mainFrame().evaluateJavaScript("loadEnd") debug("waiting for loadEnd: renderingStopped=%r loadEndOL=%r" % ( self.context.renderingStopped(), loadEndOL), 4) if loadEndOL is not None: self.mapFinished = loadEndOL else: debug("OpenlayersLayer Warning: Could not get loadEnd") if self.mapFinished: self.timerMax.stop() self.timer.stop() self.renderMap() self.finished.emit() def renderMap(self): rendererContext = self.context if rendererContext.painter().device().logicalDpiX() != int(self.outputDpi): printScale = 25.4 / self.outputDpi # OL DPI to printer pixels rendererContext.painter().scale(printScale, printScale) # render OpenLayers to image painter = QPainter(self.img) self.page.mainFrame().render(painter) painter.end() if self.img.size() != self.targetSize: targetWidth = self.targetSize.width() targetHeight = self.targetSize.height() # scale using QImage for better quality debug(" scale image: %i x %i -> %i x %i" % ( self.img.width(), self.img.height(), targetWidth, targetHeight), 3) self.img = self.img.scaled(targetWidth, targetHeight, Qt.KeepAspectRatio, Qt.SmoothTransformation) # save current state self.page.lastExtent = rendererContext.extent() self.page.lastViewPortSize = rendererContext.painter().viewport().size() self.page.lastLogicalDpi = rendererContext.painter().device().logicalDpiX() self.page.lastOutputDpi = self.outputDpi self.page.lastMapUnitsPerPixel = rendererContext.mapToPixel().mapUnitsPerPixel() def mapTimeout(self): debug("mapTimeout reached") self.timer.stop() # if not self.layerType.emitsLoadEnd: self.renderMap() self.finished.emit() def emitErrorImage(self): self.img = QImage() self.targetSize = self.img.size self.finished.emit()
class GeoGigLiveLayerRefresher(object): nProgressBarsOpen = 0 nfeaturesRead = 0 lock = threading.RLock( ) # this might not be necessary - I think this will always be happening on the same ui thread def __init__(self, connector, geogiglayer, fullDetail=False, sm_factor=1.0, sm_type="WithBBOX"): self.connector = connector self.geogiglayer = geogiglayer self.queryThread = QueryThread(self.connector) self.queryThread.started.connect(self.datasetStart) self.queryThread.finished.connect(self.datasetReceived) self.queryThread.progress_occurred.connect(self.featuresRead) self.refreshTimer = QTimer() self.refreshTimer.setSingleShot(True) self.refreshTimer.timeout.connect(self.makeQuery) self.lastExtent = None self.sm_factor = sm_factor self.sm_type = sm_type root = QgsProject.instance().layerTreeRoot() root.visibilityChanged.connect( self.visibilityChanged) # track all layer visibility changes self.fullDetail = fullDetail #root.addedChildren.connect(self.layerTreeAddedTo) # track when layer is added to tree # called when layer is removed def cleanup(self): # don't track this anymore (it causes a problem because the c++ object is # deleted, but the python object isn't) root = QgsProject.instance().layerTreeRoot() root.visibilityChanged.disconnect(self.visibilityChanged) def isLayerVisible(self): if self.geogiglayer.layer is None: return None layerId = self.geogiglayer.layer.id() if self.geogiglayer.canvas is None: treelayer = QgsProject.instance().layerTreeRoot().findLayer( layerId) # QgsLayerTreeLayer if treelayer is None: return False if not treelayer.isVisible(): return False # definitely not visible # likely visible, do a simple scale-range check return self.geogiglayer.layer.isInScaleRange( self.geogiglayer._canvas().scale()) def visibilityChanged(self, qgsLayerTreeNode): if self.isLayerVisible(): self.refresh(forceRefresh=False, tryToRepopulate=True) def openProgress(self): with self.lock: if self.nProgressBarsOpen == 0: self.nfeaturesRead = 0 qgiscommons2.gui.startProgressBar( "Transferring data from GeoGig", 0, currentWindow().messageBar()) self.nProgressBarsOpen += 1 def closeProgress(self): with self.lock: self.nProgressBarsOpen -= 1 if self.nProgressBarsOpen == 0: qgiscommons2.gui.closeProgressBar() # sometimes the progress bar can be closed by another thread/function # this will re-open it if that happens. # ex. when you have a layers being populated() during a refresh() # which can occur on project load def ensureProgressOpen(self): _progressActive = qgiscommons2.gui._progressActive if _progressActive: return # nothing to do qgiscommons2.gui.startProgressBar("Transferring data from GeoGig", 0, currentWindow().messageBar()) # called by backgrounding feature loader (self.queryThread) # this is for progress indication def featuresRead(self, nfeatsBatch): with self.lock: self.ensureProgressOpen() self.nfeaturesRead += nfeatsBatch try: qgiscommons2.gui.setProgressText( "Read " + "{:,}".format(self.nfeaturesRead) + " features...") except: pass # could be a problem... # occurs when extents change, call this from geogiglayer def refresh(self, forceRefresh=True, tryToRepopulate=False): if tryToRepopulate and not self.geogiglayer.valid: try: self.geogiglayer.populate() except: item = QgsProject.instance().layerTreeRoot().findLayer( self.geogiglayer.layer.id()) item.setItemVisibilityCheckedRecursive(False) return if not forceRefresh: extentRect = self.geogiglayer.extentToLayerCrs( self.geogiglayer._canvas().extent()) extent = [ extentRect.xMinimum(), extentRect.yMinimum(), extentRect.xMaximum(), extentRect.yMaximum() ] if self.lastExtent == extent: return # set time -- will fire after 100ms and call makeQuery if self.refreshTimer.isActive(): self.refreshTimer.setInterval(100) # restart else: self.refreshTimer.start(100) #downloads the current extent at full detail. Called when entering or exiting the editing mode. def setFullDetail(self, fullDetail, refresh=True): self.fullDetail = fullDetail if refresh: self.makeQuery() def getFullDetail(self): return self.fullDetail # thread has started to do work def datasetStart(self, url, query): self.timeStart = time.perf_counter() QgsMessageLog.logMessage("loading dataset url={}, query={}".format( url, str(query))) # return true if you shouldn't draw this layer # if its rules-based, and all the rules depend on scale, and all the rules are "out-of-scale" def doNotDrawScale(self, r, scale): if not isinstance(r, QgsRuleBasedRenderer): return False # any of them are NOT scale dependent, then need to draw if any([not r.dependsOnScale() for r in r.rootRule().children()]): return False return not any([r.isScaleOK(scale) for r in r.rootRule().children()]) def ecqlFromLayerStyle(self): canvas = self.geogiglayer._canvas() ms = canvas.mapSettings() ctx = QgsRenderContext.fromMapSettings(ms) r = self.geogiglayer.layer.renderer().clone() try: r.startRender(ctx, self.geogiglayer.layer.fields()) if self.doNotDrawScale(r, canvas.scale()): return "EXCLUDE" expression = r.filter() if expression == "" or expression == "TRUE": return None converter = ExpressionConverter(expression) return converter.asECQL() except: return None finally: r.stopRender(ctx) def makeQuery(self): self.queryThread.abort() self.queryThread.wait() # wait for it to abort if not self.isLayerVisible(): return # don't do anything if the layer is invisible NOTE: layer likely has data in it self.openProgress() extent = self.geogiglayer.extentToLayerCrs( self.geogiglayer._canvas().extent()) self.lastExtent = [ extent.xMinimum(), extent.yMinimum(), extent.xMaximum(), extent.yMaximum() ] self.queryThread.createURL(self.geogiglayer.user, self.geogiglayer.repo, self.geogiglayer.layername) if self.fullDetail: self.queryThread.createQuery(self.geogiglayer.commitid, self.lastExtent, simplifyGeom=False, ecqlFilter=self.ecqlFromLayerStyle()) else: self.queryThread.createQuery(self.geogiglayer.commitid, self.lastExtent, self.geogiglayer._canvas().width(), self.geogiglayer._canvas().height(), screenMap_factor=self.sm_factor, screenMap_type=self.sm_type, ecqlFilter=self.ecqlFromLayerStyle()) self.queryThread.start() # called by backgrounding feature loader (self.queryThread) # this is after the dataset has loaded. # None -> aborted def datasetReceived(self, memorydataset): if memorydataset is not None: end_time = time.perf_counter() QgsMessageLog.logMessage( "Dataset received ({}) - {:,} features in {}s".format( self.geogiglayer.layername, len(memorydataset), end_time - self.timeStart)) self.closeProgress() if memorydataset is None: return try: self.geogiglayer.newDatasetReceived(memorydataset) except Exception as e: QgsMessageLog.logMessage("error - " + str(e))
class AutoSuggest(QObject): def __init__(self, geturl_func, parseresult_func, parent = None): QObject.__init__(self, parent) self.geturl_func = geturl_func self.parseresult_func = parseresult_func self.editor = parent self.networkManager = QNetworkAccessManager() self.selectedObject = None self.isUnloaded = False self.popup = QTreeWidget(parent) #self.popup.setColumnCount(2) self.popup.setColumnCount(1) self.popup.setUniformRowHeights(True) self.popup.setRootIsDecorated(False) self.popup.setEditTriggers(QTreeWidget.NoEditTriggers) self.popup.setSelectionBehavior(QTreeWidget.SelectRows) self.popup.setFrameStyle(QFrame.Box | QFrame.Plain) self.popup.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.popup.header().hide() self.popup.installEventFilter(self) self.popup.setMouseTracking(True) #self.connect(self.popup, SIGNAL("itemClicked(QTreeWidgetItem*, int)"), # self.doneCompletion) self.popup.itemClicked.connect( self.doneCompletion ) self.popup.setWindowFlags(Qt.Popup) self.popup.setFocusPolicy(Qt.NoFocus) self.popup.setFocusProxy(parent) self.timer = QTimer(self) self.timer.setSingleShot(True) self.timer.setInterval(500) #self.connect(self.timer, SIGNAL("timeout()"), self.autoSuggest) self.timer.timeout.connect( self.autoSuggest ) #self.connect(self.editor, SIGNAL("textEdited(QString)"), self.timer, SLOT("start()")) #self.editor.textEdited.connect( self.timer.start ) self.editor.textEdited.connect( self.timer.start ) #self.editor.textChanged.connect( self.timer.start ) #self.connect(self.networkManager, SIGNAL("finished(QNetworkReply*)"), # self.handleNetworkData) self.networkManager.finished.connect( self.handleNetworkData ) def eventFilter(self, obj, ev): if obj != self.popup: return False if ev.type() == QEvent.MouseButtonPress: self.popup.hide() self.editor.setFocus() return True if ev.type() == QEvent.KeyPress: consumed = False key = ev.key() if key == Qt.Key_Enter or key == Qt.Key_Return: self.doneCompletion() consumed = True elif key == Qt.Key_Escape: self.editor.setFocus() self.popup.hide() consumed = True elif key in (Qt.Key_Up, Qt.Key_Down, Qt.Key_Home, Qt.Key_End, Qt.Key_PageUp, Qt.Key_PageDown): pass else: self.editor.setFocus() self.editor.event(ev) self.popup.hide() return consumed return False def showCompletion(self, rows): # Rows is an iterable of tuples like [("text",object1),("text2", object2),...] pal = self.editor.palette() color = pal.color(QPalette.Disabled, QPalette.WindowText) self.popup.setUpdatesEnabled(False) self.popup.clear() if rows is None or len( rows ) < 1: return for row in rows: item = QTreeWidgetItem(self.popup) item.setText(0, row[0]) #item.setText(1, hit['type']) item.setTextAlignment(1, Qt.AlignRight) item.setForeground(1, color) item.setData(2, Qt.UserRole, (row[1],)) # Try immutable py obj #http://stackoverflow.com/questions/9257422/how-to-get-the-original-python-data-from-qvariant self.popup.setCurrentItem(self.popup.topLevelItem(0)) self.popup.resizeColumnToContents(0) #self.popup.resizeColumnToContents(1) self.popup.adjustSize() self.popup.setUpdatesEnabled(True) h = self.popup.sizeHintForRow(0) * min(15, len(rows)) + 3 w = max(self.popup.width(), self.editor.width()) self.popup.resize(w, h) self.popup.move(self.editor.mapToGlobal(QPoint(0, self.editor.height()))) self.popup.setFocus() self.popup.show() def doneCompletion(self): self.timer.stop() self.popup.hide() self.editor.setFocus() item = self.popup.currentItem() if item: self.editor.setText(item.text(0) ) obj = item.data(2, Qt.UserRole) #.toPyObject() self.selectedObject = obj[0] e = QKeyEvent(QEvent.KeyPress, Qt.Key_Enter, Qt.NoModifier) QApplication.postEvent(self.editor, e) e = QKeyEvent(QEvent.KeyRelease, Qt.Key_Enter, Qt.NoModifier) QApplication.postEvent(self.editor, e) def preventSuggest(self): self.timer.stop() def autoSuggest(self): term = self.editor.text() if term: qurl = self.geturl_func( term ) if qurl: # TODO: Cancel existing requests: http://qt-project.org/forums/viewthread/18073 self.networkManager.get(QNetworkRequest( qurl )) #QUrl(url))) def handleNetworkData(self, networkReply): url = networkReply.url() #print "received url:", url.toString() if not networkReply.error(): response = networkReply.readAll() pystring = str(response, 'utf-8') #print "Response: ", response rows = self.parseresult_func( pystring ) self.showCompletion( rows ) networkReply.deleteLater() def unload( self ): # Avoid processing events after QGIS shutdown has begun self.popup.removeEventFilter(self) self.isUnloaded = True
class Downloader(QObject): NOT_FOUND = 0 NO_ERROR = 0 TIMEOUT_ERROR = 4 UNKNOWN_ERROR = -1 replyFinished = pyqtSignal(str, int, int) def __init__(self, parent=None): QObject.__init__(self, parent) self.queue = [] self.redirected_urls = {} self.requestingUrls = [] self.replies = [] self.eventLoop = QEventLoop() self.sync = False self.fetchedFiles = {} self.clearCounts() self.timer = QTimer() self.timer.setSingleShot(True) self.timer.timeout.connect(self.fetchTimedOut) # network settings self.userAgent = "QuickMapServices tile layer (+https://github.com/nextgis/quickmapservices)" self.max_connection = 4 self.default_cache_expiration = 24 self.errorStatus = Downloader.NO_ERROR def clearCounts(self): self.fetchSuccesses = 0 self.fetchErrors = 0 self.cacheHits = 0 def fetchTimedOut(self): self.log("Downloader.timeOut()") self.abort() self.errorStatus = Downloader.TIMEOUT_ERROR def abort(self): # clear queue and abort sent requests self.queue = [] self.timer.stop() for reply in self.replies: reply.abort() self.errorStatus = Downloader.UNKNOWN_ERROR def replyFinishedSlot(self): reply = self.sender() url = reply.request().url().toString() self.log("replyFinishedSlot: %s" % url) if not url in self.fetchedFiles: self.fetchedFiles[url] = None self.requestingUrls.remove(url) self.replies.remove(reply) isFromCache = 0 httpStatusCode = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) if reply.error() == QNetworkReply.NoError: if httpStatusCode == 301: new_url = str(reply.rawHeader("Location")) self.addToQueue(new_url, url) else: self.fetchSuccesses += 1 if reply.attribute(QNetworkRequest.SourceIsFromCacheAttribute): self.cacheHits += 1 isFromCache = 1 elif not reply.hasRawHeader("Cache-Control"): cache = QgsNetworkAccessManager.instance().cache() if cache: metadata = cache.metaData(reply.request().url()) # self.log("Expiration date: " + metadata.expirationDate().toString().encode("utf-8")) if metadata.expirationDate().isNull(): metadata.setExpirationDate( QDateTime.currentDateTime().addSecs(self.default_cache_expiration * 60 * 60)) cache.updateMetaData(metadata) self.log( "Default expiration date has been set: %s (%d h)" % (url, self.default_cache_expiration)) if reply.isReadable(): data = reply.readAll() if self.redirected_urls.has_key(url): url = self.redirected_urls[url] self.fetchedFiles[url] = data else: qDebug("http status code: " + str(httpStatusCode)) # self.emit(SIGNAL('replyFinished(QString, int, int)'), url, reply.error(), isFromCache) self.replyFinished.emit(url, reply.error(), isFromCache) else: if self.sync and httpStatusCode == 404: self.fetchedFiles[url] = self.NOT_FOUND self.fetchErrors += 1 if self.errorStatus == self.NO_ERROR: self.errorStatus = self.UNKNOWN_ERROR reply.deleteLater() if debug_mode: qDebug("queue: %d, requesting: %d" % (len(self.queue), len(self.requestingUrls))) if len(self.queue) + len(self.requestingUrls) == 0: # all replies have been received if self.sync: self.logT("eventLoop.quit()") self.eventLoop.quit() else: self.timer.stop() elif len(self.queue) > 0: # start fetching the next file self.fetchNext() self.log("replyFinishedSlot End: %s" % url) def fetchNext(self): if len(self.queue) == 0: return url = self.queue.pop(0) self.log("fetchNext: %s" % url) request = QNetworkRequest(QUrl(url)) request.setRawHeader("User-Agent", self.userAgent) reply = QgsNetworkAccessManager.instance().get(request) reply.finished.connect(self.replyFinishedSlot) self.requestingUrls.append(url) self.replies.append(reply) return reply def fetchFiles(self, urlList, timeout_ms=0): self.log("fetchFiles()") self.sync = True self.queue = [] self.redirected_urls = {} self.clearCounts() self.errorStatus = Downloader.NO_ERROR self.fetchedFiles = {} if len(urlList) == 0: return self.fetchedFiles for url in urlList: self.addToQueue(url) for i in range(self.max_connection): self.fetchNext() if timeout_ms > 0: self.timer.setInterval(timeout_ms) self.timer.start() self.logT("eventLoop.exec_(): " + str(self.eventLoop)) self.eventLoop.exec_() self.log("fetchFiles() End: %d" % self.errorStatus) if timeout_ms > 0: self.timer.stop() return self.fetchedFiles def addToQueue(self, url, redirected_from=None): if url in self.queue: return False self.queue.append(url) if redirected_from is not None: self.redirected_urls[url] = redirected_from return True def queueCount(self): return len(self.queue) def finishedCount(self): return len(self.fetchedFiles) def unfinishedCount(self): return len(self.queue) + len(self.requestingUrls) def log(self, msg): if debug_mode: qDebug(msg) def logT(self, msg): if debug_mode: qDebug("%s: %s" % (str(threading.current_thread()), msg)) def fetchFilesAsync(self, urlList, timeout_ms=0): self.log("fetchFilesAsync()") self.sync = False self.queue = [] self.clearCounts() self.errorStatus = Downloader.NO_ERROR self.fetchedFiles = {} if len(urlList) == 0: return self.fetchedFiles for url in urlList: self.addToQueue(url) for i in range(self.max_connection): self.fetchNext() if timeout_ms > 0: self.timer.setInterval(timeout_ms) self.timer.start()
class HuntRegister: """QGIS Plugin Implementation.""" def __init__(self, iface): """Constructor. :param iface: An interface instance that will be passed to this class which provides the hook by which you can manipulate the QGIS application at run time. :type iface: QgsInterface """ # Save reference to the QGIS interface self.iface = iface # initialize plugin directory self.plugin_dir = os.path.dirname(__file__) # Check if plugin was started the first time in current QGIS session # Must be set in initGui() to survive plugin reloads self.first_start = None # This plugin depends on alkisplugin self.alkisplugin = None # Members self.alkisToolBar = None # the toolbar instance where the alkisplugin and huntplugin QAction symbols are placed self.alkisSelectAreaLayer = None # QgsVectorLayer selected parcels of alkisplugin self.huntSelectAreaLayer = None # QgsVectorLayer selected parcels of huntplugin # help web view self.helpiew = None # webview containing manuals # All actions are assigned to alter self.huntSelectAreaLayer self.hswapAction = None # single QAction copy selected parcels from alkisplugin to huntplugin self.hAddMarkAction = None # checkable QAction select and unselect parcels self.hlistAction = None # single QAction select parcels by parcel attributes self.hclearAction = None # single QAction unselect all selected parcels self.hownerAction = None # single QAction get parcel certificates for all selected parcels self.hhuntAction = None # single QAction create a hunt register self.hinfoAction = None # single QAction get a basic summary of all selected parcels self.helpAction = None # single QAction open help files webview browser window # self.testAction = None # single QAction used for testing program fragments self.hAddMarkTool = None # click recognizing map tool for self.hAddMarkAction self.core = None # function core for this plugin self.initTimer = QTimer( self.iface ) # timer used to init self.huntSelectAreaLayer dynamically when alkis layers are added self.initTimer.setInterval(1000) # 1 sec interval self.initTimer.timeout.connect(self.initLayers) self.init() # init main instances def init(self): """init main instances""" if (alkisAvailable): try: self.alkisplugin = Alkis.alkisplugin.alkisplugin( self.iface) # create alkisplugin object self.alkisplugin.queryOwnerAction = QAction( None ) # this is used in akisplugin "opendb" and must therefore be set to prevent runtime errors except AttributeError: QMessageBox.critical( None, "Fehler", "norGIS ALKIS-Einbindung zuerst aktivieren und \"JagdKataster\" erneut aktivieren" ) raise AttributeError("alkisplugin not active") else: QMessageBox.critical( None, "Fehler", "norGIS ALKIS-Einbindung installieren und zuerst aktivieren. Dann \"JagdKataster\" erneut aktivieren" ) raise AttributeError("alkisplugin not installed") self.core = HuntCore(self) # function core for this plugin def initGui(self): """Create the menu entries and toolbar icons inside the QGIS GUI.""" # will be set False in run() self.first_start = True self.helpview = QWebView() self.helpview.resize(1280, 850) self.helpview.setWindowTitle("JagdKataster Anleitungen") help_dir = os.path.join(self.plugin_dir, "help", "build", "html", "index.html") self.helpview.load(QUrl.fromLocalFile(help_dir)) # the toolbar entries of this plugin should be placed alongside the alkisplugin entries # therefore the alkisplugin toolbar is derived tBars = self.iface.mainWindow().findChildren( QToolBar) # get all toolbars from main window self.alkisToolBar = next( (n for n in tBars if n.objectName() == "norGIS_ALKIS_Toolbar"), None ) # find the instance of alkisplugin toolbar by its static name if self.alkisToolBar is None: # in case the toolbar is not yet loaded create the instance with its static name # create alkis toolbar in case it is not yet loaded self.alkisToolBar = self.iface.addToolBar(u"norGIS: ALKIS") self.alkisToolBar.setObjectName("norGIS_ALKIS_Toolbar") #create toolbar items self.hswapAction = QAction(QIcon("hunt:mark_transfer.svg"), "Flächenmarkierung übertragen", self.iface.mainWindow()) self.hswapAction.setWhatsThis( "Flächenmarkierung (gelb) nach Jagd-Flächenmarkierung (blau) übertragen" ) self.hswapAction.setStatusTip( "Flächenmarkierung (gelb) nach Jagd-Flächenmarkierung (blau) übertragen" ) self.hswapAction.triggered.connect(lambda: self.core.swapAlkisToHunt()) self.alkisToolBar.addAction(self.hswapAction) self.hAddMarkAction = QAction(QIcon("hunt:mark_add.svg"), u"Flurstück (de)selektieren", self.iface.mainWindow()) self.hAddMarkAction.setWhatsThis( "Flurstück in Jagd-Flächenmarkierung selektieren oder deselektieren" ) self.hAddMarkAction.setStatusTip( "Flurstück in Jagd-Flächenmarkierung selektieren oder deselektieren" ) self.hAddMarkAction.setCheckable(True) self.hAddMarkAction.triggered.connect( lambda: self.iface.mapCanvas().setMapTool(self.hAddMarkTool)) self.alkisToolBar.addAction(self.hAddMarkAction) self.hAddMarkTool = HAdd(self) self.hAddMarkTool.setAction(self.hAddMarkAction) self.hlistAction = QAction(QIcon("hunt:mark_list.svg"), "Selektieren nach Flurstückseigenschaft", self.iface.mainWindow()) self.hlistAction.setWhatsThis( "Selektierung der Flurstücke in Jagd-Flächenmarkierung anhand Flurstückseigenschaften" ) self.hlistAction.setStatusTip( "Selektierung der Flurstücke in Jagd-Flächenmarkierung anhand Flurstückseigenschaften" ) self.hlistAction.triggered.connect( lambda: self.core.showListSelection()) self.alkisToolBar.addAction(self.hlistAction) self.hclearAction = QAction(QIcon("hunt:mark_clear.svg"), "Alle deselektieren", self.iface.mainWindow()) self.hclearAction.setWhatsThis( "Alle Flurstücke in Jagd-Flächenmarkierung deselektieren") self.hclearAction.setStatusTip( "Alle Flurstücke in Jagd-Flächenmarkierung deselektieren") self.hclearAction.triggered.connect(lambda: self.core.clearHighlight()) self.alkisToolBar.addAction(self.hclearAction) self.hownerAction = QAction(QIcon("hunt:mark_own.svg"), "Flurstücksnachweise anzeigen", self.iface.mainWindow()) self.hownerAction.setWhatsThis( "Flurstücksnachweise für selektierte Flurstücke in Jagd-Flächenmarkierung anzeigen" ) self.hownerAction.setStatusTip( "Flurstücksnachweise für selektierte Flurstücke in Jagd-Flächenmarkierung anzeigen" ) self.hownerAction.triggered.connect( lambda: self.core.showParcelCerts()) self.alkisToolBar.addAction(self.hownerAction) self.hhuntAction = QAction(QIcon("hunt:mark_hunt.svg"), "Jagdkataster erstellen", self.iface.mainWindow()) self.hhuntAction.setWhatsThis( "Jagdkataster für selektierte Flurstücke in Jagd-Flächenmarkierung erstellen" ) self.hhuntAction.setStatusTip( "Jagdkataster für selektierte Flurstücke in Jagd-Flächenmarkierung erstellen" ) self.hhuntAction.triggered.connect(lambda: self.core.showHuntReg()) self.alkisToolBar.addAction(self.hhuntAction) self.hinfoAction = QAction(QIcon("hunt:mark_info.svg"), "Flurstückszusammenfassung anzeigen", self.iface.mainWindow()) self.hinfoAction.setWhatsThis( "Flurstückszusammenfassung für selektierte Flurstücke in Jagd-Flächenmarkierung anzeigen" ) self.hinfoAction.setStatusTip( "Flurstückszusammenfassung für selektierte Flurstücke in Jagd-Flächenmarkierung anzeigen" ) self.hinfoAction.triggered.connect( lambda: self.core.showSummaryDialog()) self.alkisToolBar.addAction(self.hinfoAction) self.helpAction = QAction(QIcon("hunt:logo.svg"), "Anleitungen", self.iface.mainWindow()) self.helpAction.setWhatsThis("JagdKataster-Anleitungen") self.helpAction.setStatusTip("JagdKataster-Anleitungen") self.helpAction.triggered.connect(lambda: self.helpview.show()) # self.testAction = QAction(QIcon("hunt:test.svg"), "Test", self.iface.mainWindow()) # self.testAction.setWhatsThis("Test action") # self.testAction.setStatusTip("Test action") # self.testAction.triggered.connect(lambda: self.core.test(self.huntSelectAreaLayer)) # self.alkisToolBar.addAction(self.testAction) self.iface.addPluginToDatabaseMenu("&ALKIS", self.helpAction) QgsProject.instance().layersAdded.connect( self.initTimer.start ) # react to changes in the layer tree. Maybe the alkis layers were added QgsProject.instance().layersWillBeRemoved.connect( self.layersRemoved ) # remove entries in case this plugin layers are to be removed def initLayers(self): """init self.huntSelectAreaLayer in case the alkis layers from alkisplugin are loaded""" self.initTimer.stop( ) # this methode may be called by a timer started when layers are added => stop the timer after first timeout event if self.alkisSelectAreaLayer is None: # are alkisplugin layers loaded ad readable from entry? (layerId, res) = QgsProject.instance().readEntry("alkis", "/areaMarkerLayer") if res and layerId: self.alkisSelectAreaLayer = QgsProject.instance().mapLayer( layerId) if self.huntSelectAreaLayer is None: # is the huntplugin layer already loaded? (layerId, res) = QgsProject.instance().readEntry("hunt", "/areaMarkerLayer") if res and layerId: self.huntSelectAreaLayer = QgsProject.instance().mapLayer( layerId) if self.huntSelectAreaLayer is None and self.alkisSelectAreaLayer is not None: # alkisplugin layers are loaded but huntplugin layer is not self.createLayer() # create huntplugin layer def layersRemoved(self, layersIds): """remove entries and references in case this plugin layers are to be removed""" if self.alkisSelectAreaLayer is not None and self.alkisSelectAreaLayer.id( ) in layersIds: self.alkisSelectAreaLayer = None if self.huntSelectAreaLayer is not None and self.huntSelectAreaLayer.id( ) in layersIds: QgsProject.instance().removeEntry("hunt", "/areaMarkerLayer") self.core.hlayer = None self.huntSelectAreaLayer = None def createLayer(self): """create and add huntplugin layer to the layer tree""" if (self.alkisSelectAreaLayer is not None): parent = QgsProject.instance().layerTreeRoot().findLayer( self.alkisSelectAreaLayer).parent() layeropts = QgsVectorLayer.LayerOptions(False, False) self.init( ) # reinit main instances because alkis instance conninfo might have changed (db, conninfo) = self.core.openDB() if db is None: return self.huntSelectAreaLayer = QgsVectorLayer( u"%s estimatedmetadata=true checkPrimaryKeyUnicity=0 key='ogc_fid' type=MULTIPOLYGON srid=%d table=%s.po_polygons (polygon) sql=false" % (conninfo, self.alkisplugin.epsg, self.alkisplugin.quotedschema()), u"Jagd-Flächenmarkierung", "postgres", layeropts) sym = QgsSymbol.defaultSymbol(QgsWkbTypes.PolygonGeometry) sym.setColor(Qt.blue) sym.setOpacity(0.3) self.huntSelectAreaLayer.setRenderer(QgsSingleSymbolRenderer(sym)) QgsProject.instance().addMapLayer(self.huntSelectAreaLayer, False) parent.insertLayer(0, self.huntSelectAreaLayer) self.core.hlayer = None QgsProject.instance().writeEntry("hunt", "/areaMarkerLayer", self.huntSelectAreaLayer.id()) def unload(self): """Removes the plugin menu item and icon from QGIS GUI.""" if self.hswapAction: self.hswapAction.deleteLater() self.hswapAction = None if self.hAddMarkAction: self.hAddMarkAction.deleteLater() self.hAddMarkAction = None if self.hlistAction: self.hlistAction.deleteLater() self.hlistAction = None if self.hclearAction: self.hclearAction.deleteLater() self.hclearAction = None if self.hownerAction: self.hownerAction.deleteLater() self.hownerAction = None if self.hhuntAction: self.hhuntAction.deleteLater() self.hhuntAction = None if self.hinfoAction: self.hinfoAction.deleteLater() self.hinfoAction = None if self.helpAction: self.helpAction.deleteLater() self.helpAction = None # if self.testAction: # self.testAction.deleteLater() # self.testAction = None QgsProject.instance().layersAdded.disconnect(self.initTimer.start) QgsProject.instance().layersWillBeRemoved.disconnect( self.layersRemoved) def run(self): """Run method""" if self.first_start: self.first_start = False
class QmsServiceToolbox(QDockWidget, FORM_CLASS): def __init__(self, iface): QDockWidget.__init__(self, iface.mainWindow()) self.setupUi(self) self.newsFrame.setVisible(False) self.iface = iface self.search_threads = None # [] self.extent_renderer = RubberBandResultRenderer() self.cmbStatusFilter.addItem(self.tr('All'), STATUS_FILTER_ALL) self.cmbStatusFilter.addItem(self.tr('Valid'), STATUS_FILTER_ONLY_WORKS) self.cmbStatusFilter.currentIndexChanged.connect(self.start_search) if hasattr(self.txtSearch, 'setPlaceholderText'): self.txtSearch.setPlaceholderText(self.tr("Search string...")) self.delay_timer = QTimer(self) self.delay_timer.setSingleShot(True) self.delay_timer.setInterval(250) self.delay_timer.timeout.connect(self.start_search) self.txtSearch.textChanged.connect(self.delay_timer.start) self.btnFilterByExtent.toggled.connect(self.toggle_filter_button) self.one_process_work = QMutex() self.add_last_used_services() self.show_news() def show_news(self): client = Client() client.set_proxy(*QGISSettings.get_qgis_proxy()) qms_news = client.get_news() if qms_news is None: self.newsFrame.setVisible(False) return news = News(qms_news) if news.is_time_to_show(): self.newsLabel.setText(news.html) self.newsFrame.setVisible(True) else: self.newsFrame.setVisible(False) def toggle_filter_button(self, checked): self.txtSearch.setDisabled(checked) if checked: self.iface.mapCanvas().extentsChanged.connect(self.start_search) self.iface.mapCanvas().destinationCrsChanged.connect(self.start_search) self.start_search() else: self.iface.mapCanvas().extentsChanged.disconnect(self.start_search) self.iface.mapCanvas().destinationCrsChanged.disconnect(self.start_search) def start_search(self): search_text = None geom_filter = None # status filter status_filter = None sel_value = self.cmbStatusFilter.itemData(self.cmbStatusFilter.currentIndex()) if sel_value != STATUS_FILTER_ALL: status_filter = sel_value if not self.btnFilterByExtent.isChecked(): # text search search_text = unicode(self.txtSearch.text()) if not search_text: self.lstSearchResult.clear() # self.clearSearchResult() self.add_last_used_services() return else: # extent filter extent = self.iface.mapCanvas().extent() map_crs = getCanvasDestinationCrs(self.iface) if map_crs.postgisSrid() != 4326: crsDest = QgsCoordinateReferenceSystem(4326) # WGS 84 xform = QgsCoordinateTransform(map_crs, crsDest) extent = xform.transform(extent) geom_filter = extent.asWktPolygon() if self.search_threads: self.search_threads.data_downloaded.disconnect() self.search_threads.search_finished.disconnect() self.search_threads.stop() self.search_threads.wait() self.lstSearchResult.clear() # self.clearSearchResult() searcher = SearchThread(search_text, self.one_process_work, parent=self.iface.mainWindow(), geom_filter=geom_filter, status_filter=status_filter) searcher.data_downloaded.connect(self.show_result) searcher.error_occurred.connect(self.show_error) searcher.search_started.connect(self.search_started_process) searcher.search_finished.connect(self.search_finished_progress) self.search_threads = searcher searcher.start() def add_last_used_services(self): services = CachedServices().get_cached_services() if len(services) == 0: return self.lstSearchResult.insertItem(0, self.tr("Last used:")) # l = QLabel(self.tr("Last used:")) # l.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) # self.lSearchResult.addWidget(l) for attributes, image_qByteArray in services: custom_widget = QmsSearchResultItemWidget( attributes, image_qByteArray ) new_item = QListWidgetItem(self.lstSearchResult) new_item.setSizeHint(custom_widget.sizeHint()) self.lstSearchResult.addItem(new_item) self.lstSearchResult.setItemWidget( new_item, custom_widget ) # self.lSearchResult.addWidget(custom_widget) # w = QWidget() # w.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) # self.lSearchResult.addWidget(w) def search_started_process(self): self.lstSearchResult.clear() self.lstSearchResult.insertItem(0, self.tr('Searching...')) def search_finished_progress(self): self.lstSearchResult.takeItem(0) if self.lstSearchResult.count() == 0: new_widget = QLabel() new_widget.setTextFormat(Qt.RichText) new_widget.setOpenExternalLinks(True) new_widget.setWordWrap(True) new_widget.setText( u"<div align='center'> <strong>{}</strong> </div><div align='center' style='margin-top: 3px'> {} </div>".format( self.tr(u"No results."), self.tr(u"You can add a service to become searchable. Start <a href='{}'>here</a>.").format( u"https://qms.nextgis.com/create" ), ) ) new_item = QListWidgetItem(self.lstSearchResult) new_item.setSizeHint(new_widget.sizeHint()) self.lstSearchResult.addItem(new_item) self.lstSearchResult.setItemWidget( new_item, new_widget ) def show_result(self, geoservice, image_ba): if geoservice: custom_widget = QmsSearchResultItemWidget(geoservice, image_ba, extent_renderer=self.extent_renderer) new_item = QListWidgetItem(self.lstSearchResult) new_item.setSizeHint(custom_widget.sizeHint()) self.lstSearchResult.addItem(new_item) self.lstSearchResult.setItemWidget( new_item, custom_widget ) else: new_item = QListWidgetItem() new_item.setText(self.tr('No results!')) new_item.setData(Qt.UserRole, None) self.lstSearchResult.addItem(new_item) self.lstSearchResult.update() def show_error(self, error_text): self.lstSearchResult.clear() new_widget = QLabel() new_widget.setTextFormat(Qt.RichText) new_widget.setOpenExternalLinks(True) new_widget.setWordWrap(True) new_widget.setText( u"<div align='center'> <strong>{}</strong> </div><div align='center' style='margin-top: 3px'> {} </div>".format( self.tr('Error'), error_text ) ) new_item = QListWidgetItem(self.lstSearchResult) new_item.setSizeHint(new_widget.sizeHint()) self.lstSearchResult.addItem(new_item) self.lstSearchResult.setItemWidget( new_item, new_widget )
class ValidatingLineEdit(QLineEdit): ''' Custom QLineEdit control that validates user input against database values as the user types. ''' # Signal raised when the user input is invalid. invalidatedInput = pyqtSignal() def __init__(self, parent=None, notificationbar=None, dbmodel=None, attrname=None): ''' :param parent: Parent widget :param notificationbar: instance of stdm.ui.NotificationBar class. ''' QLineEdit.__init__(self, parent) self._notifBar = notificationbar self._dbmodel = None self._attrName = None self._timer = QTimer(self) self._timer.setInterval(800) self._timer.setSingleShot(True) self._invalidMsg = "" self._filterOperator = "=" self._defaultStyleSheet = self.styleSheet() self._isValid = True self._currInvalidMsg = "" # Connect signals self._timer.timeout.connect(self.validateInput) self.textChanged.connect(self.onTextChanged) def validateInput(self): ''' Validate user input. ''' if self._dbmodel: if callable(self._dbmodel): modelObj = self._dbmodel() # Then it is a class instance else: modelObj = self._dbmodel self._dbmodel = self._dbmodel.__class__ objQueryProperty = getattr(self._dbmodel, self._attrName) modelRecord = modelObj.queryObject().filter( func.lower(objQueryProperty) == func.lower( self.text())).first() if modelRecord != None: self.setStyleSheet(INVALIDATESTYLESHEET) self._currInvalidMsg = self._invalidMsg.format("'" + self.text() + "'") self._isValid = False if self._notifBar: self._notifBar.insertErrorNotification( self._currInvalidMsg) def setInvalidMessage(self, message): ''' The message to be displayed when the user input is invalid. ''' self._invalidMsg = message def invalidMessage(self): ''' Returns the invalidation message for the control. ''' return self._invalidMsg def setDatabaseModel(self, dbmodel): ''' Set database model which should be callable. ''' self._dbmodel = dbmodel def setAttributeName(self, attrname): ''' Attribute name of the database model for validating against. ''' self._attrName = attrname def setModelAttr(self, model, attributeName): ''' Set a callable model class and attribute name. ''' self._dbmodel = model self._attrName = attributeName def setNotificationBar(self, notifBar): ''' Sets the notification bar. ''' self._notifBar = notifBar def setQueryOperator(self, queryOp): ''' Specify a string-based value for the filter operator that validates the user input. ''' self._filterOperator = queryOp def queryOperator(self): ''' Return the current query operator. Default is '=' operator. ''' return self._filterOperator def validate(self): ''' Convenience method that can be used to validate the current state of the control. ''' if not self._isValid: if self._notifBar: self._notifBar.insertErrorNotification(self._currInvalidMsg) return False else: return True def onTextChanged(self, userText): ''' Slot raised whenever the text changes in the control. ''' self.setStyleSheet(self._defaultStyleSheet) self._isValid = True if self._notifBar != None: self._notifBar.clear() self._timer.start()
class VisualizerDock(QDockWidget, FORM_CLASS): # Evento para quando o plugin é fechado closingPlugin = pyqtSignal() def __init__(self, iface): super(VisualizerDock, self).__init__() self.iface = iface self.request_error = False self.timer = QTimer() self.timer.setSingleShot(True) self.timer.setInterval(7000) self.timer.timeout.connect(lambda: self.lb_status.setText('Pronto')) self.setupUi(self) self.list_resource = ResourceTreeWidgetDecorator(self.list_resource) self.load_resources_from_model() # Eventos self.bt_add_resource.clicked.connect(self._bt_add_resource_clicked) self.bt_remove_resource.clicked.connect(self._bt_remove_resource_clicked) self.list_resource.setContextMenuPolicy(Qt.CustomContextMenu) self.list_resource.customContextMenuRequested.connect(self.open_context_menu) self.list_resource.leaf_resource_double_clicked.connect(self._list_resource_doubleClicked) #self.tx_quick_resource.returnPressed.connect(self._tx_quick_resource_pressed) def _tx_quick_resource_pressed(self): def extract_name(url): return url.strip(' /').split('/')[-1] url = self.tx_quick_resource.text() self.tx_quick_resource.clear() if url: name = extract_name(url) self.add_resource(name, url) def _list_resource_doubleClicked(self, item): name, iri = item.text(0), item.text(1) self.open_operations_editor(name, iri) def _bt_add_resource_clicked(self): self.open_add_resource_dialog() def _bt_remove_resource_clicked(self): selected_items = self.list_resource.selectedItems() if not selected_items: return confirm = MessageBox.question(u'Deseja realmente remover o recurso selecionado?', u'Remover Recurso') if confirm: memorized_urls = Config.get('memo_urls') item_name = selected_items[0].text(0) if item_name in memorized_urls: index = self.list_resource.indexOfTopLevelItem(selected_items[0]) self.list_resource.takeTopLevelItem(index) memorized_urls.pop(item_name) Config.update_dict('memo_urls', memorized_urls) def load_resource(self, resource): parent_item = self.list_resource.add(resource) parent_item.set_color_user_resource() def add_resource(self, name, url): resource = ResourceManager.load(url, name) self.load_resource(resource) Config.set('memo_urls', {name: url}) def load_resources_from_model(self): model = Config.get('memo_urls') if not model: return for name, iri in model.items(): resource = ResourceManager.load(iri, name) self.load_resource(resource) def open_context_menu(self, position): item = self.list_resource.itemAt(position) if not item: return resource = ResourceManager.load(item.url(), item.name()) is_leaf_node = item.childCount() == 0 if is_leaf_node: self.show_context_menu(item, resource, position) else: self.show_entry_point_menu(item, resource, position) def show_context_menu(self, item, resource, where): menu = QMenu() # Load layers action action_open_layer = QAction(self.tr(u'Carregar camada'), None) action_open_layer.triggered.connect(lambda: self._load_layer_on_qgis(resource)) menu.addAction(action_open_layer) # Load layers as... # action_open_layer_as = QAction(self.tr(u'Carregar camada como...'), None) # action_open_layer_as.triggered.connect(lambda: self._load_layer_from_url(item.text(0), item.text(1))) # menu.addAction(action_open_layer_as) menu.addSeparator() # Montar Operações action_open_editor = QAction(self.tr(u'Montar operações'), None) action_open_editor.triggered.connect(lambda: self.open_operations_editor(item.text(0), item.text(1))) menu.addAction(action_open_editor) menu.addSeparator() action_edit = QAction(self.tr(u'Editar'), None) action_edit.triggered.connect(lambda: self.open_edit_dialog(item)) menu.addAction(action_edit) menu.exec_(self.list_resource.viewport().mapToGlobal(where)) def show_entry_point_menu(self, item, resource, where): menu = QMenu() action_edit = QAction(self.tr(u'Editar'), None) action_edit.triggered.connect(lambda: self.open_edit_dialog(item)) menu.addAction(action_edit) menu.exec_(self.list_resource.viewport().mapToGlobal(where)) def open_edit_dialog(self, item): dialog_edit_resource = DialogEditResource(item) dialog_edit_resource.accepted.connect(self._resource_edited) dialog_edit_resource.exec_() return dialog_edit_resource def open_operations_editor(self, name, url): resource = ResourceManager.load(url, name) dialog_construct_url = DialogConstructUrl(resource) dialog_construct_url.load_url_command.connect(lambda n, i: self._load_layer_from_iri(dialog_construct_url, n, i)) dialog_construct_url.exec_() return dialog_construct_url def open_add_resource_dialog(self): dialog_add_resource = DialogAddResource() dialog_add_resource.accepted.connect(self.add_resource) dialog_add_resource.exec_() return dialog_add_resource def _resource_edited(self, tree_item, new_name, new_url): memo = Config.get('memo_urls') old = memo.pop(tree_item.text(0)) new = {new_name: new_url} memo.update(new) Config.update_dict('memo_urls', memo) tree_item.setText(0, new_name) tree_item.setText(1, new_url) def _load_layer_from_iri(self, widget, name, iri): try: dummy = ResourceManager.load(iri, name) self._load_layer_on_qgis(dummy) widget.close() except Exception as e: raise def _load_layer_on_qgis(self, resource): def request_failed(error): self.request_error = error self.update_status(u'Requisição retornou um erro') MessageBox.critical(error, u'Requisição retornou um erro') self.request_error = False self.timer.stop() resource.request_started.connect(self.start_request) resource.request_progress.connect(self.download_progress) resource.request_error.connect(request_failed) resource.request_finished.connect(self.trigger_reset_status) # Trigger download of data and events for user feedback resource.data() if not self.request_error: layer = Plugin.create_layer(resource) if layer: Layer.add(layer) if layer.featureCount() == 0: Logging.info(u'{} retornou conjunto vazio de dados'.format(resource.iri), u'IBGEVisualizer') MessageBox.info(u'URL retornou conjunto vazio') else: raise Exception(self.request_error) def start_request(self): self.request_error = False self.timer.stop() self.lb_status.setText(u'Enviando requisição e aguardando resposta...') def update_status(self, msg): self.lb_status.setText(msg) def download_progress(self, received, total): if not self.request_error: if received == total: msg = u'Concluído ' + received else: msg = u'Baixando recurso... ' + received + (' / ' + total if total != '-1.0' else '') self.update_status(msg) def trigger_reset_status(self): if not self.request_error: #self.request_error = False self.timer.start() def close_event(self, event): self.closingPlugin.emit() event.accept() def run(self): self.iface.addDockWidget(Qt.RightDockWidgetArea, self) self.show()
class AutoSuggest(QObject): def __init__(self, geturl_func, parseresult_func, parent=None): QObject.__init__(self, parent) self.geturl_func = geturl_func self.parseresult_func = parseresult_func self.editor = parent self.networkManager = QNetworkAccessManager() self.selectedObject = None self.isUnloaded = False self.popup = QTreeWidget(parent) #self.popup.setColumnCount(2) self.popup.setColumnCount(1) self.popup.setUniformRowHeights(True) self.popup.setRootIsDecorated(False) self.popup.setEditTriggers(QTreeWidget.NoEditTriggers) self.popup.setSelectionBehavior(QTreeWidget.SelectRows) self.popup.setFrameStyle(QFrame.Box | QFrame.Plain) self.popup.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.popup.header().hide() self.popup.installEventFilter(self) self.popup.setMouseTracking(True) #self.connect(self.popup, SIGNAL("itemClicked(QTreeWidgetItem*, int)"), # self.doneCompletion) self.popup.itemClicked.connect(self.doneCompletion) self.popup.setWindowFlags(Qt.Popup) self.popup.setFocusPolicy(Qt.NoFocus) self.popup.setFocusProxy(parent) self.timer = QTimer(self) self.timer.setSingleShot(True) self.timer.setInterval(500) #self.connect(self.timer, SIGNAL("timeout()"), self.autoSuggest) self.timer.timeout.connect(self.autoSuggest) #self.connect(self.editor, SIGNAL("textEdited(QString)"), self.timer, SLOT("start()")) #self.editor.textEdited.connect( self.timer.start ) self.editor.textEdited.connect(self.timer.start) #self.editor.textChanged.connect( self.timer.start ) #self.connect(self.networkManager, SIGNAL("finished(QNetworkReply*)"), # self.handleNetworkData) self.networkManager.finished.connect(self.handleNetworkData) def eventFilter(self, obj, ev): if obj != self.popup: return False if ev.type() == QEvent.MouseButtonPress: self.popup.hide() self.editor.setFocus() return True if ev.type() == QEvent.KeyPress: consumed = False key = ev.key() if key == Qt.Key_Enter or key == Qt.Key_Return: self.doneCompletion() consumed = True elif key == Qt.Key_Escape: self.editor.setFocus() self.popup.hide() consumed = True elif key in (Qt.Key_Up, Qt.Key_Down, Qt.Key_Home, Qt.Key_End, Qt.Key_PageUp, Qt.Key_PageDown): pass else: self.editor.setFocus() self.editor.event(ev) self.popup.hide() return consumed return False def showCompletion(self, rows): # Rows is an iterable of tuples like [("text",object1),("text2", object2),...] pal = self.editor.palette() color = pal.color(QPalette.Disabled, QPalette.WindowText) self.popup.setUpdatesEnabled(False) self.popup.clear() if rows is None or len(rows) < 1: return for row in rows: item = QTreeWidgetItem(self.popup) item.setText(0, row[0]) #item.setText(1, hit['type']) item.setTextAlignment(1, Qt.AlignRight) item.setForeground(1, color) item.setData( 2, Qt.UserRole, (row[1], ) ) # Try immutable py obj #http://stackoverflow.com/questions/9257422/how-to-get-the-original-python-data-from-qvariant self.popup.setCurrentItem(self.popup.topLevelItem(0)) self.popup.resizeColumnToContents(0) #self.popup.resizeColumnToContents(1) self.popup.adjustSize() self.popup.setUpdatesEnabled(True) h = self.popup.sizeHintForRow(0) * min(15, len(rows)) + 3 w = max(self.popup.width(), self.editor.width()) self.popup.resize(w, h) self.popup.move( self.editor.mapToGlobal(QPoint(0, self.editor.height()))) self.popup.setFocus() self.popup.show() def doneCompletion(self): self.timer.stop() self.popup.hide() self.editor.setFocus() item = self.popup.currentItem() if item: self.editor.setText(item.text(0)) obj = item.data(2, Qt.UserRole) #.toPyObject() self.selectedObject = obj[0] e = QKeyEvent(QEvent.KeyPress, Qt.Key_Enter, Qt.NoModifier) QApplication.postEvent(self.editor, e) e = QKeyEvent(QEvent.KeyRelease, Qt.Key_Enter, Qt.NoModifier) QApplication.postEvent(self.editor, e) def preventSuggest(self): self.timer.stop() def autoSuggest(self): term = self.editor.text() if term: qurl = self.geturl_func(term) if qurl: # TODO: Cancel existing requests: http://qt-project.org/forums/viewthread/18073 self.networkManager.get(QNetworkRequest(qurl)) #QUrl(url))) def handleNetworkData(self, networkReply): url = networkReply.url() #print "received url:", url.toString() if not networkReply.error(): response = networkReply.readAll() pystring = str(response, 'utf-8') #print "Response: ", response rows = self.parseresult_func(pystring) self.showCompletion(rows) networkReply.deleteLater() def unload(self): # Avoid processing events after QGIS shutdown has begun self.popup.removeEventFilter(self) self.isUnloaded = True
class ColorsDock(QgsDockWidget): """ Custom dock widget for modfication of project colors """ def __init__(self, iface): super().__init__() self.iface = iface stack = QgsPanelWidgetStack() self.main_panel = QgsPanelWidget() self.main_panel.setDockMode(True) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setMargin(0) # pylint: disable=no-value-for-parameter self.main_panel.setLayout(layout) self.setWindowTitle(self.tr('Project Colors')) self.setObjectName('project_colors_dock') scheme = [ s for s in QgsApplication.colorSchemeRegistry().schemes() if isinstance(s, QgsProjectColorScheme) ] self.color_list = QgsColorSchemeList(None, scheme[0]) layout.addWidget(self.color_list) # defer updates for a short timeout -- prevents flooding with signals # when doing lots of color changes, improving app responsiveness self.timer = QTimer(self) self.timer.setSingleShot(True) self.timer.setInterval(100) self.block_updates = False def apply_colors(): """ Applies color changes to canvas and project """ self.block_updates = True self.color_list.saveColorsToScheme() self.block_updates = False self.iface.mapCanvas().refreshAllLayers() self.timer.timeout.connect(apply_colors) def colors_changed(): """ Triggers a deferred update of the project colors """ if self.timer.isActive(): return self.timer.start() self.color_list.model().dataChanged.connect(colors_changed) stack.setMainPanel(self.main_panel) self.setWidget(stack) QgsProject.instance().projectColorsChanged.connect( self.repopulate_list) def repopulate_list(self): """ Rebuild the colors list when project colors are changed """ if self.block_updates: return scheme = [ s for s in QgsApplication.colorSchemeRegistry().schemes() if isinstance(s, QgsProjectColorScheme) ][0] self.color_list.setScheme(scheme)
class NotificationBar(QObject, object): ''' Used to display notifications in a vertical layout in order for important user messages to be inserted in a widget. By default, the notification(s) will be removed after 10 seconds. To change this default behaviour, change the value of the 'timerinterval' parameter in the constructor. ''' userClosed = pyqtSignal() onShow = pyqtSignal() onClear = pyqtSignal() def __init__(self, layout, timerinterval=10000): QObject.__init__(self) self.interval = timerinterval if isinstance(layout, QVBoxLayout): self.layout = layout self.layout.setSpacing(2) # Set notification type stylesheet self.errorStyleSheet = "background-color: #FFBABA;" self.error_font_color = 'color: #D8000C;' self.successStyleSheet = "background-color: #DFF2BF;" self.success_font_color = 'color: #4F8A10;' self.informationStyleSheet = "background-color: #BDE5F8;" self.information_font_color = 'color: #555;' self.warningStyleSheet = "background-color: #FEEFB3;" self.warning_font_color = 'color: #9F6000;' # Timer settings self.timer = QTimer(self.layout) self.timer.setInterval(self.interval) self.timer.timeout.connect(self.clear) else: self.layout = None self._notifications = {} def insertNotification(self, message, notificationType): ''' Insert a message into notification bar/layout ''' if self.layout != None: notificationItem = NotificationItem() notificationItem.messageClosed.connect(self.onNotificationClosed) font_color = "color: rgb(255, 255, 255);" frameStyle = "background-color: rgba(255, 255, 255, 0);" if notificationType == ERROR: frameStyle = self.errorStyleSheet font_color = self.error_font_color elif notificationType == SUCCESS: frameStyle = self.successStyleSheet font_color = self.success_font_color elif notificationType == INFORMATION: frameStyle = self.informationStyleSheet font_color = self.information_font_color elif notificationType == WARNING: frameStyle = self.warningStyleSheet font_color = self.warning_font_color notificationItem.setMessage( message, notificationType, frameStyle, font_color ) self.layout.addWidget(notificationItem) self._notifications[ str(notificationItem.code) ] = notificationItem # Reset the timer self.timer.start() self.onShow.emit() def insertErrorNotification(self, message): ''' A convenience method for inserting error messages ''' self.insertNotification(message, ERROR) def insertWarningNotification(self, message): ''' A convenience method for inserting warning messages ''' self.insertNotification(message, WARNING) def insertSuccessNotification(self, message): ''' A convenience method for inserting information messages ''' self.insertNotification(message, SUCCESS) def insertInformationNotification(self, message): ''' A convenience method for inserting information messages ''' self.insertNotification(message, INFORMATION) def clear(self): ''' Remove all notifications. ''' if self.layout != None: for code, lbl in self._notifications.items(): self.layout.removeWidget(lbl) lbl.setVisible(False) lbl.deleteLater() self._notifications = {} self.onClear.emit() def onNotificationClosed(self, code): ''' Slot raised when a user chooses to close a notification item. Prevents an error from occurring when removing all notifications from the container. ''' strCode = str(code) if strCode in self._notifications: del self._notifications[strCode] self.userClosed.emit()
class QtWaitingSpinner(QWidget): mColor = QColor(Qt.black) mRoundness = 120.0 mMinimumTrailOpacity = 25.0 mTrailFadePercentage = 60.0 mRevolutionsPerSecond = 1.0 mNumberOfLines = 20 mLineLength = 20 mLineWidth = 2 mInnerRadius = 60 mCurrentCounter = 0 mIsSpinning = False def __init__(self, parent=None, centerOnParent=True, disableParentWhenSpinning=True, *args, **kwargs): QWidget.__init__(self, parent=parent, *args, **kwargs) self.mCenterOnParent = centerOnParent self.mDisableParentWhenSpinning = disableParentWhenSpinning self.initialize() def initialize(self): self.timer = QTimer(self) self.timer.timeout.connect(self.rotate) self.updateSize() self.updateTimer() self.hide() @pyqtSlot() def rotate(self): self.mCurrentCounter += 1 if self.mCurrentCounter > self.numberOfLines(): self.mCurrentCounter = 0 self.update() def updateSize(self): size = (self.mInnerRadius + self.mLineLength) * 2 self.setFixedSize(size, size) def updateTimer(self): self.timer.setInterval( 1000 / (self.mNumberOfLines * self.mRevolutionsPerSecond)) seconds = 1000 / (self.mNumberOfLines * self.mRevolutionsPerSecond) def updatePosition(self): if self.parentWidget() and self.mCenterOnParent: self.move(self.parentWidget().width() / 2 - self.width() / 2, self.parentWidget().height() / 2 - self.height() / 2) def lineCountDistanceFromPrimary(self, current, primary, totalNrOfLines): distance = primary - current if distance < 0: distance += totalNrOfLines return distance def currentLineColor(self, countDistance, totalNrOfLines, trailFadePerc, minOpacity, color): if countDistance == 0: return color minAlphaF = minOpacity / 100.0 distanceThreshold = ceil((totalNrOfLines - 1) * trailFadePerc / 100.0) if countDistance > distanceThreshold: color.setAlphaF(minAlphaF) else: alphaDiff = self.mColor.alphaF() - minAlphaF gradient = alphaDiff / distanceThreshold + 1.0 resultAlpha = color.alphaF() - gradient * countDistance resultAlpha = min(1.0, max(0.0, resultAlpha)) color.setAlphaF(resultAlpha) return color def paintEvent(self, event): self.updatePosition() painter = QPainter(self) painter.fillRect(self.rect(), Qt.transparent) painter.setRenderHint(QPainter.Antialiasing, True) if self.mCurrentCounter > self.mNumberOfLines: self.mCurrentCounter = 0 painter.setPen(Qt.NoPen) for i in range(self.mNumberOfLines): painter.save() painter.translate(self.mInnerRadius + self.mLineLength, self.mInnerRadius + self.mLineLength) rotateAngle = 360.0 * i / self.mNumberOfLines painter.rotate(rotateAngle) painter.translate(self.mInnerRadius, 0) distance = self.lineCountDistanceFromPrimary( i, self.mCurrentCounter, self.mNumberOfLines) color = self.currentLineColor(distance, self.mNumberOfLines, self.mTrailFadePercentage, self.mMinimumTrailOpacity, self.mColor) painter.setBrush(color) painter.drawRoundedRect( QRect(0, -self.mLineWidth // 2, self.mLineLength, self.mLineLength), self.mRoundness, Qt.RelativeSize) painter.restore() def start(self): self.updatePosition() self.mIsSpinning = True self.show() if self.parentWidget() and self.mDisableParentWhenSpinning: self.parentWidget().setEnabled(False) if not self.timer.isActive(): self.timer.start() self.mCurrentCounter = 0 def stop(self): self.mIsSpinning = False self.hide() if self.parentWidget() and self.mDisableParentWhenSpinning: self.parentWidget().setEnabled(True) if self.timer.isActive(): self.timer.stop() self.mCurrentCounter = 0 def setNumberOfLines(self, lines): self.mNumberOfLines = lines self.updateTimer() def setLineLength(self, length): self.mLineLength = length self.updateSize() def setLineWidth(self, width): self.mLineWidth = width self.updateSize() def setInnerRadius(self, radius): self.mInnerRadius = radius self.updateSize() def color(self): return self.mColor def roundness(self): return self.mRoundness def minimumTrailOpacity(self): return self.mMinimumTrailOpacity def trailFadePercentage(self): return self.mTrailFadePercentage def revolutionsPersSecond(self): return self.mRevolutionsPerSecond def numberOfLines(self): return self.mNumberOfLines def lineLength(self): return self.mLineLength def lineWidth(self): return self.mLineWidth def innerRadius(self): return self.mInnerRadius def isSpinning(self): return self.mIsSpinning def setRoundness(self, roundness): self.mRoundness = min(0.0, max(100, roundness)) def setColor(self, color): self.mColor = color def setRevolutionsPerSecond(self, revolutionsPerSecond): self.mRevolutionsPerSecond = revolutionsPerSecond self.updateTimer() def setTrailFadePercentage(self, trail): self.mTrailFadePercentage = trail def setMinimumTrailOpacity(self, minimumTrailOpacity): self.mMinimumTrailOpacity = minimumTrailOpacity
class LessonsCreator(object): def __init__(self, iface): self.iface = iface # add tests to tester plugin try: from qgistester.tests import addTestModule from lessonscreator.test import testerplugin addTestModule(testerplugin, "LessonsCreator") except Exception as e: pass self.capturing = False def unload(self): self.iface.removePluginMenu(u"Lessons", self.action) del self.action del self.newStepAction QgsApplication.instance().focusChanged.disconnect( self.processFocusChanged) try: from qgistester.tests import removeTestModule from lessonscreator.test import testerplugin removeTestModule(testerplugin, "LessonsCreator") except Exception as e: pass def initGui(self): lessonIcon = QIcon(os.path.dirname(__file__) + '/edit.png') self.action = QAction(lessonIcon, "Capture lesson steps", self.iface.mainWindow()) self.action.triggered.connect(self.toggleCapture) self.iface.addPluginToMenu(u"Lessons", self.action) QgsApplication.instance().focusChanged.connect( self.processFocusChanged) self.newStepAction = QAction("New step", self.iface.mainWindow()) self.newStepAction.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_W)) self.newStepAction.setShortcutContext(Qt.ApplicationShortcut) self.newStepAction.triggered.connect(self.startNewStep) self.iface.mainWindow().addAction(self.newStepAction) connections = [] iScreenshot = 0 iStep = 0 outputHtmlFile = None outputPyFile = None def startNewStep(self): if self.outputHtmlFile: self.outputHtmlFile.close() self.iStep += 1 path = os.path.join(self.folder, "step_%i.html" % self.iStep) self.outputHtmlFile = open(path, "w") self.outputPyFile.write( '''lesson.addStep("Step_%i", "step_%i.html", steptype=Step.MANUALSTEP)\n''' % (self.iStep, self.iStep)) def toggleCapture(self): if self.capturing: self.action.setText("Capture lesson steps") self.capturing = False self.outputHtmlFile.close() self.outputPyFile.close() self.outputHtmlFile = None self.outputPyFile = None else: self.folder = QFileDialog.getExistingDirectory( self.iface.mainWindow(), "Select folder to store lesson") if not self.folder: return path = os.path.join(self.folder, "__init__.py") self.outputPyFile = open(path, "w") template = ( "from lessons.lesson import Lesson, Step\n" "from lessons.utils import *\n" "lesson = Lesson('Lesson', 'Basic lessons', 'lesson.html')\n\n" ) self.outputPyFile.write(template) self.iScreenshot = 0 self.iStep = 0 self.updateConnections() self.action.setText("Stop capturing lesson steps") self.capturing = True self.startNewStep() def processWidgetClick(self, obj): if self.capturing: try: text = "Click on '%s'" % obj.text() except Exception as e: # fix_print_with_import print(e) text = "Click on " + str(obj) self.outputHtmlFile.write("<p>%s</p>\n" % text) self.updateConnections() lastComboText = None def processComboNewSelection(self, combo): if self.capturing: text = "Select '%s' in '%s'" % (combo.currentText(), combo.objectName()) if text == self.lastComboText: return self.lastComboText = text self.outputHtmlFile.write("<p>%s</p>\n" % text) self.createScreenshot(combo.parent(), combo.frameGeometry()) self.updateConnections() def processCheckBoxChange(self, check): if self.capturing: if check.isChecked(): text = "Check the '%s' checkbox" % (check.text()) else: text = "Uncheck the '%s' checkbox" % (check.text()) self.outputHtmlFile.write("<p>%s</p>\n" % text) self.createScreenshot(check.parent(), check.frameGeometry()) def processMenuClick(self, action): if self.capturing and action.text() != "Stop capturing lesson steps": text = "Click on menu '%s'" % action.text() self.outputHtmlFile.write("<p>%s</p>\n" % text) def getParentWindow(self, obj): window = None try: parent = obj.parent() while parent is not None: if isinstance(parent, QDialog): window = parent break parent = parent.parent() except: window = None return window or QgsApplication.instance().desktop() def processFocusChanged(self, old, new): if self.capturing: self.updateConnections() if isinstance(old, QLineEdit) and old.text().strip(): text = "Enter '%s' in textbox '%s'" % (old.text(), old.objectName()) self.outputHtmlFile.write("<p>%s</p>\n" % text) self.createScreenshot(old.parent(), old.frameGeometry()) else: oldParent = self.getParentWindow(old) newParent = self.getParentWindow(new) # fix_print_with_import print(oldParent, newParent) if oldParent != newParent: self.createScreenshot(newParent) elif isinstance( new, (QLineEdit, QTextEdit, QComboBox, QSpinBox, QRadioButton)): text = "Select the '%s' widget" % (old.objectName() or str(old)) self.outputHtmlFile.write("<p>%s</p>\n" % text) self.createScreenshot(new.parent(), new.frameGeometry()) timer = None def _createScreenshot(self, obj, rect): pixmap = QPixmap.grabWindow(obj.winId()).copy() if rect is not None: painter = QPainter() painter.begin(pixmap) painter.setPen(QPen(QBrush(Qt.red), 3, Qt.DashLine)) painter.drawRect(rect) painter.end() pixmap.save(os.path.join(self.folder, '%i.jpg' % self.iScreenshot), 'jpg') self.outputHtmlFile.write("<img src='%i.jpg'/>\n" % self.iScreenshot) self.iScreenshot += 1 self.timer = None def createScreenshot(self, obj, rect=None): if self.capturing and self.timer is None: self.timer = QTimer() self.timer.setInterval(1000) self.timer.setSingleShot(True) self.timer.timeout.connect( lambda: self._createScreenshot(obj, rect)) self.timer.start() def updateConnections(self): widgets = QgsApplication.instance().allWidgets() for w in widgets: if w not in self.connections: if isinstance(w, (QPushButton, QToolButton)): f = partial(self.processWidgetClick, w) w.clicked.connect(f) self.connections.append(w) elif isinstance(w, QComboBox): f = partial(self.processComboNewSelection, w) w.currentIndexChanged.connect(f) self.connections.append(w) elif isinstance(w, QCheckBox): f = partial(self.processCheckBoxChange, w) w.stateChanged.connect(f) self.connections.append(w) menuActions = [] actions = self.iface.mainWindow().menuBar().actions() for action in actions: menuActions.extend(self.getActions(action, None)) for action, menu in menuActions: if menu not in self.connections: menu.triggered.connect(self.processMenuClick) self.connections.append(menu) def getActions(self, action, menu): menuActions = [] submenu = action.menu() if submenu is None: menuActions.append((action, menu)) return menuActions else: actions = submenu.actions() for subaction in actions: if subaction.menu() is not None: menuActions.extend( self.getActions(subaction, subaction.menu())) elif not subaction.isSeparator(): menuActions.append((subaction, submenu)) return menuActions
class TimeManagerGuiControl(QObject): """This class controls all plugin-related GUI elements. Emitted signals are defined here.""" showOptions = pyqtSignal() signalExportVideo = pyqtSignal(str, int, bool, bool, bool) toggleTime = pyqtSignal() toggleArchaeology = pyqtSignal() back = pyqtSignal() forward = pyqtSignal() play = pyqtSignal() signalCurrentTimeUpdated = pyqtSignal(QDateTime) signalSliderTimeChanged = pyqtSignal(float) signalTimeFrameType = pyqtSignal(str) signalTimeFrameSize = pyqtSignal(int) signalSaveOptions = pyqtSignal() signalArchDigitsSpecified = pyqtSignal(int) signalArchCancelled = pyqtSignal() def __init__(self, iface, model): """Initialize the GUI control""" QObject.__init__(self) self.iface = iface self.model = model self.optionsDialog = None self.path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'ui') # load the form self.dock = uic.loadUi(os.path.join(self.path, DOCK_WIDGET_FILE)) self.iface.addDockWidget(Qt.BottomDockWidgetArea, self.dock) self.dock.pushButtonExportVideo.setEnabled(False) # only enabled if there are managed layers self.dock.pushButtonOptions.clicked.connect(self.optionsClicked) self.dock.pushButtonExportVideo.clicked.connect(self.exportVideoClicked) self.dock.pushButtonToggleTime.clicked.connect(self.toggleTimeClicked) self.dock.pushButtonArchaeology.clicked.connect(self.archaeologyClicked) self.dock.pushButtonBack.clicked.connect(self.backClicked) self.dock.pushButtonForward.clicked.connect(self.forwardClicked) self.dock.pushButtonPlay.clicked.connect(self.playClicked) self.dock.dateTimeEditCurrentTime.dateTimeChanged.connect(self.currentTimeChangedDateText) # self.dock.horizontalTimeSlider.valueChanged.connect(self.currentTimeChangedSlider) self.sliderTimer = QTimer(self) self.sliderTimer.setInterval(250) self.sliderTimer.setSingleShot(True) self.sliderTimer.timeout.connect(self.currentTimeChangedSlider) self.dock.horizontalTimeSlider.valueChanged.connect(self.startTimer) self.dock.comboBoxTimeExtent.currentIndexChanged[str].connect(self.currentTimeFrameTypeChanged) self.dock.spinBoxTimeExtent.valueChanged.connect(self.currentTimeFrameSizeChanged) # this signal is responsible for rendering the label self.iface.mapCanvas().renderComplete.connect(self.renderLabel) # create shortcuts self.focusSC = QShortcut(QKeySequence("Ctrl+Space"), self.dock) self.focusSC.activated.connect(self.dock.horizontalTimeSlider.setFocus) # put default values self.dock.horizontalTimeSlider.setMinimum(conf.MIN_TIMESLIDER_DEFAULT) self.dock.horizontalTimeSlider.setMaximum(conf.MAX_TIMESLIDER_DEFAULT) self.dock.dateTimeEditCurrentTime.setMinimumDate(MIN_QDATE) self.showLabel = conf.DEFAULT_SHOW_LABEL self.exportEmpty = conf.DEFAULT_EXPORT_EMPTY self.labelOptions = TimestampLabelConfig(self.model) # placeholders for widgets that are added dynamically self.bcdateSpinBox = None # add to plugins toolbar try: self.action = QAction(QCoreApplication.translate("TimeManagerGuiControl", "Toggle visibility"), self.iface.mainWindow()) self.action.triggered.connect(self.toggleDock) self.iface.addPluginToMenu(QCoreApplication.translate("TimeManagerGuiControl", "&TimeManager"), self.action) except Exception as e: pass # OK for testing def startTimer(self): self.sliderTimer.start() def getLabelFormat(self): return self.labelOptions.fmt def getLabelFont(self): return self.labelOptions.font def getLabelSize(self): return self.labelOptions.size def getLabelColor(self): return self.labelOptions.color def getLabelBgColor(self): return self.labelOptions.bgcolor def getLabelPlacement(self): return self.labelOptions.placement def setLabelFormat(self, fmt): if not fmt: return self.labelOptions.fmt = fmt def setLabelFont(self, font): if not font: return self.labelOptions.font = font def setLabelSize(self, size): if not size: return self.labelOptions.size = size def setLabelColor(self, color): if not color: return self.labelOptions.color = color def setLabelBgColor(self, bgcolor): if not bgcolor: return self.labelOptions.bgcolor = bgcolor def setLabelPlacement(self, placement): if not placement: return self.labelOptions.placement = placement def toggleDock(self): self.dock.setVisible(not self.dock.isVisible()) def getOptionsDialog(self): return self.optionsDialog def showAnimationOptions(self): self.animationDialog = uic.loadUi(os.path.join(self.path, ANIMATION_WIDGET_FILE)) def selectFile(): self.animationDialog.lineEdit.setText(QFileDialog.getOpenFileName()) self.animationDialog.pushButton.clicked.connect(self.selectAnimationFolder) self.animationDialog.buttonBox.accepted.connect(self.sendAnimationOptions) self.animationDialog.show() def selectAnimationFolder(self): prev_directory = TimeManagerProjectHandler.plugin_setting(conf.LAST_ANIMATION_PATH_TAG) if prev_directory: self.animationDialog.lineEdit.setText(QFileDialog.getExistingDirectory(directory=prev_directory)) else: self.animationDialog.lineEdit.setText(QFileDialog.getExistingDirectory()) def sendAnimationOptions(self): path = self.animationDialog.lineEdit.text() if path == "": self.showAnimationOptions() TimeManagerProjectHandler.set_plugin_setting(conf.LAST_ANIMATION_PATH_TAG, path) delay_millis = self.animationDialog.spinBoxDelay.value() export_gif = self.animationDialog.radioAnimatedGif.isChecked() export_video = self.animationDialog.radioVideo.isChecked() do_clear = self.animationDialog.clearCheckBox.isChecked() self.signalExportVideo.emit(path, delay_millis, export_gif, export_video, do_clear) def showLabelOptions(self): options = self.labelOptions self.dialog = QDialog() lo = uic.loadUiType(os.path.join(self.path, LABEL_OPTIONS_WIDGET_FILE))[0] self.labelOptionsDialog = lo() self.labelOptionsDialog.setupUi(self.dialog) self.labelOptionsDialog.fontsize.setValue(options.size) self.labelOptionsDialog.time_format.setText(options.fmt) self.labelOptionsDialog.font.setCurrentFont(QFont(options.font)) self.labelOptionsDialog.placement.addItems(TimestampLabelConfig.PLACEMENTS) self.labelOptionsDialog.placement.setCurrentIndex(TimestampLabelConfig.PLACEMENTS.index(options.placement)) self.labelOptionsDialog.text_color.setColor(QColor(options.color)) self.labelOptionsDialog.bg_color.setColor(QColor(options.bgcolor)) self.labelOptionsDialog.buttonBox.accepted.connect(self.saveLabelOptions) self.dialog.show() def saveLabelOptions(self): self.labelOptions.font = self.labelOptionsDialog.font.currentFont().family() self.labelOptions.size = self.labelOptionsDialog.fontsize.value() self.labelOptions.bgcolor = self.labelOptionsDialog.bg_color.color().name() self.labelOptions.color = self.labelOptionsDialog.text_color.color().name() self.labelOptions.placement = self.labelOptionsDialog.placement.currentText() self.labelOptions.fmt = self.labelOptionsDialog.time_format.text() if self.labelOptionsDialog.radioButton_dt.isChecked(): self.labelOptions.type = "dt" if self.labelOptionsDialog.radioButton_beginning.isChecked(): self.labelOptions.type = "beginning" if self.labelOptionsDialog.radioButton_epoch.isChecked(): self.labelOptions.type = "epoch" def enableArchaeologyTextBox(self): self.dock.dateTimeEditCurrentTime.dateTimeChanged.connect(self.currentTimeChangedDateText) if self.bcdateSpinBox is None: self.bcdateSpinBox = self.createBCWidget(self.dock) self.bcdateSpinBox.editingFinished.connect(self.currentBCYearChanged) self.replaceWidget(self.dock.horizontalLayout, self.dock.dateTimeEditCurrentTime, self.bcdateSpinBox, 5) def getTimeWidget(self): if time_util.is_archaelogical(): return self.bcdateSpinBox else: return self.dock.dateTimeEditCurrentTime def currentBCYearChanged(self): val = self.bcdateSpinBox.text() try: bcdate_util.BCDate.from_str(val, strict_zeros=False) self.signalCurrentTimeUpdated.emit(val) except Exception as e: warn("Invalid bc date: {}".format(val)) # how to mark as such? return def disableArchaeologyTextBox(self): if self.bcdateSpinBox is None: return self.replaceWidget(self.dock.horizontalLayout, self.bcdateSpinBox, self.dock.dateTimeEditCurrentTime, 5) def createBCWidget(self, mainWidget): newWidget = QLineEdit(mainWidget) # QtGui.QSpinBox(mainWidget) # newWidget.setMinimum(-1000000) # newWidget.setValue(-1) newWidget.setText("0001 BC") return newWidget def replaceWidget(self, layout, oldWidget, newWidget, idx): """ Replaces oldWidget with newWidget at layout at index idx The way it is done, the widget is not destroyed and the connections to it remain """ layout.removeWidget(oldWidget) oldWidget.close() # I wonder if this has any memory leaks? </philosoraptor> layout.insertWidget(idx, newWidget) newWidget.show() layout.update() def optionsClicked(self): self.showOptions.emit() def exportVideoClicked(self): self.showAnimationOptions() def toggleTimeClicked(self): self.toggleTime.emit() def archaeologyClicked(self): self.toggleArchaeology.emit() def showArchOptions(self): self.archMenu = uic.loadUi(os.path.join(self.path, ARCH_WIDGET_FILE)) self.archMenu.buttonBox.accepted.connect(self.saveArchOptions) self.archMenu.buttonBox.rejected.connect(self.cancelArch) self.archMenu.show() def saveArchOptions(self): self.signalArchDigitsSpecified.emit(self.archMenu.numDigits.value()) def cancelArch(self): self.signalArchCancelled.emit() def backClicked(self): self.back.emit() def forwardClicked(self): self.forward.emit() def playClicked(self): if self.dock.pushButtonPlay.isChecked(): self.dock.pushButtonPlay.setIcon(QIcon("TimeManager:images/pause.png")) else: self.dock.pushButtonPlay.setIcon(QIcon("TimeManager:images/play.png")) self.play.emit() def currentTimeChangedSlider(self): sliderVal = self.dock.horizontalTimeSlider.value() try: pct = (sliderVal - self.dock.horizontalTimeSlider.minimum()) * 1.0 / ( self.dock.horizontalTimeSlider.maximum() - self.dock.horizontalTimeSlider.minimum()) except Exception as e: # slider is not properly initialized yet return if self.model.getActiveDelimitedText() and qgs.getVersion() < conf.MIN_DTEXT_FIXED: time.sleep(0.1) # hack to fix issue in qgis core with delimited text which was fixed in 2.9 self.signalSliderTimeChanged.emit(pct) def currentTimeChangedDateText(self, qdate): # info("changed time via text") self.signalCurrentTimeUpdated.emit(qdate) def currentTimeFrameTypeChanged(self, frameType): self.signalTimeFrameType.emit(frameType) def currentTimeFrameSizeChanged(self, frameSize): if frameSize < 1: # time frame size = 0 is meaningless self.dock.spinBoxTimeExtent.setValue(1) return self.signalTimeFrameSize.emit(frameSize) def unload(self): """Unload the plugin""" self.iface.removeDockWidget(self.dock) self.iface.removePluginMenu("TimeManager", self.action) def setWindowTitle(self, title): self.dock.setWindowTitle(title) def showOptionsDialog(self, layerList, animationFrameLength, playBackwards=False, loopAnimation=False): """Show the optionsDialog and populate it with settings from timeLayerManager""" # load the form self.optionsDialog = uic.loadUi(os.path.join(self.path, OPTIONS_WIDGET_FILE)) # restore settings from layerList: for layer in layerList: settings = layer_settings.getSettingsFromLayer(layer) layer_settings.addSettingsToRow(settings, self.optionsDialog.tableWidget) self.optionsDialog.tableWidget.resizeColumnsToContents() # restore animation options self.optionsDialog.spinBoxFrameLength.setValue(animationFrameLength) self.optionsDialog.checkBoxBackwards.setChecked(playBackwards) self.optionsDialog.checkBoxLabel.setChecked(self.showLabel) self.optionsDialog.checkBoxDontExportEmpty.setChecked(not self.exportEmpty) self.optionsDialog.checkBoxLoop.setChecked(loopAnimation) self.optionsDialog.show_label_options_button.clicked.connect(self.showLabelOptions) self.optionsDialog.checkBoxLabel.stateChanged.connect(self.showOrHideLabelOptions) self.optionsDialog.textBrowser.setHtml(QCoreApplication.translate('TimeManager', """\ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html> <head> <meta name="qrichtext" content="1"/> <style> li.mono { font-family: Consolas, Courier New, Courier, monospace; } </style> </head> <body> <h1>Time Manager</h1> <p>Time Manager filters your layers and displays only layers and features that match the specified time frame. Time Manager supports vector layers and raster layers (including WMS-T).</p> <p>Timestamps have to be in one of the following formats:</p> <ul> <li class="mono">%Y-%m-%d %H:%M:%S.%f</li> <li class="mono">%Y-%m-%d %H:%M:%S</li> <li class="mono">%Y-%m-%d %H:%M</li> <li class="mono">%Y-%m-%dT%H:%M:%S</li> <li class="mono">%Y-%m-%dT%H:%M:%SZ</li> <li class="mono">%Y-%m-%dT%H:%M</li> <li class="mono">%Y-%m-%dT%H:%MZ</li> <li class="mono">%Y-%m-%d</li> <li class="mono">%Y/%m/%d %H:%M:%S.%f</li> <li class="mono">%Y/%m/%d %H:%M:%S</li> <li class="mono">%Y/%m/%d %H:%M</li> <li class="mono">%Y/%m/%d</li> <li class="mono">%H:%M:%S</li> <li class="mono">%H:%M:%S.%f</li> <li class="mono">%Y.%m.%d %H:%M:%S.%f</li> <li class="mono">%Y.%m.%d %H:%M:%S</li> <li class="mono">%Y.%m.%d %H:%M</li> <li class="mono">%Y.%m.%d</li> <li class="mono">%Y%m%d%H%M%SED</li> <li>Integer timestamp in seconds after or before the epoch (1970-1-1)</li> </ul> <p>The layer list contains all layers managed by Time Manager. To add a vector layer, press [Add layer]. To add a raster layer, press [Add raster]. If you want to remove a layer from the list, select it and press [Remove layer].</p> <p>Below the layer list, you'll find the following <b>animation options</b>:</p> <p><b>Show frame for x milliseconds</b>... allows you to adjust for how long a frame will be visible during the animation</p> <p><b>Play animation backwards</b>... if checked, the animation will run in reverse direction</p> <p><b>Display frame start time on map</b>... if checked, displays the start time of the visible frame in the lower right corner of the map</p> <h2>Add Layer dialog</h2> <p>Here, you are asked to select the layer that should be added and specify the columns containing start and (optionally) end time.</p> <p>The <b>offset</b> option allows you to further time the appearance of features. If you specify an offset of -1, the features will appear one second later than they would by default.</p> <h2>Dock widget</h2> <p>The dock was designed to attach to the bottom of the QGIS main window. It offers the following tools:</p> <ul> <li><img src="images/power_on.png" alt="power"/> ... On/Off switch, allows you to turn Time Manager's functionality on/off with the click of only one button</li> <li><span class="hidden">[Settings]</span><input type="button" value="Settings"/> ... opens the Settings dialog where you can manage your spatio-temporal layers</li> <li><span class="hidden">[Export Video]</span><input type="button" value="Export Video"/> ... exports an image series based on current settings (This button is only enabled if there are layers registered in Time Manager "Settings")</li> <li><b>Time frame start: <span class="hidden">[2000-01-01 00:00:00]</span></b><input type="text" value="2000-01-01 00:00:00"/> ... shows the start time of the currently active frame. Allows you to precisely specify your desired analysis time.</li> <li><b>Time frame size: </b><input type="text" value="1"/><span class="hidden">[x]</span><select><option value="days">days</option></select> ... allow you to choose the size of the time frame</li> <li><img src="images/back.png" alt="back"/> ... go to the previous time frame</li> <li><img src="images/forward.png" alt="forward"/> ... go to the next time frame</li> <li><b>Slider</b> ... shows the position of current frame relative to the whole dataset and allows you to seamlessly scroll through the dataset</li> <li><img src="images/play.png" alt="play"/> ... start an automatic animation based on your current settings</li> </ul> </body> </html>""")) # show dialog self.showOrHideLabelOptions() self.optionsDialog.show() # create raster and vector dialogs self.vectorDialog = VectorLayerDialog(self.iface, os.path.join(self.path, ADD_VECTOR_LAYER_WIDGET_FILE), self.optionsDialog.tableWidget) self.rasterDialog = RasterLayerDialog(self.iface, os.path.join(self.path, ADD_RASTER_LAYER_WIDGET_FILE), self.optionsDialog.tableWidget) # establish connections self.optionsDialog.pushButtonAddVector.clicked.connect(self.vectorDialog.show) self.optionsDialog.pushButtonAddRaster.clicked.connect(self.rasterDialog.show) self.optionsDialog.pushButtonRemove.clicked.connect(self.removeLayer) self.optionsDialog.buttonBox.accepted.connect(self.saveOptions) # self.optionsDialog.buttonBox.helpRequested.connect(self.showHelp) def showOrHideLabelOptions(self): self.optionsDialog.show_label_options_button.setEnabled( self.optionsDialog.checkBoxLabel.isChecked()) def saveOptions(self): """Save the options from optionsDialog to timeLayerManager""" self.signalSaveOptions.emit() def removeLayer(self): """Remove currently selected layer (= row) from options""" currentRow = self.optionsDialog.tableWidget.currentRow() try: layerName = self.optionsDialog.tableWidget.item(currentRow, 0).text() except AttributeError: # if no row is selected return if QMessageBox.question(self.optionsDialog, QCoreApplication.translate("TimeManagerGuiControl", "Remove Layer"), QCoreApplication.translate("TimeManagerGuiControl", "Do you really want to remove layer {}?").format(layerName), QMessageBox.Ok, QMessageBox.Cancel) == QMessageBox.Ok: self.optionsDialog.tableWidget.removeRow(self.optionsDialog.tableWidget.currentRow()) def disableAnimationExport(self): """Disable the animation export button""" self.dock.pushButtonExportVideo.setEnabled(False) def enableAnimationExport(self): """Enable animation export button""" self.dock.pushButtonExportVideo.setEnabled(True) def refreshMapCanvas(self, sender=None): """Refresh the map canvas""" # QMessageBox.information(self.iface.mainWindow(),'Test Output','Refresh!\n'+str(sender)) self.iface.mapCanvas().refresh() def setTimeFrameSize(self, frameSize): """Set spinBoxTimeExtent to given frameSize""" self.dock.spinBoxTimeExtent.setValue(frameSize) def setTimeFrameType(self, frameType): """Set comboBoxTimeExtent to given frameType""" i = self.dock.comboBoxTimeExtent.findText(frameType) self.dock.comboBoxTimeExtent.setCurrentIndex(i) def setActive(self, isActive): """Toggle pushButtonToggleTime""" self.dock.pushButtonToggleTime.setChecked(isActive) def setArchaeologyPressed(self, isActive): """Toggle pushButtonArchaeology""" self.dock.pushButtonArchaeology.setChecked(isActive) def addActionShowSettings(self, action): """Add action to pushButttonOptions""" self.dock.pushButtonOptions.addAction(action) def turnPlayButtonOff(self): """Turn pushButtonPlay off""" if self.dock.pushButtonPlay.isChecked(): self.dock.pushButtonPlay.toggle() self.dock.pushButtonPlay.setIcon(QIcon("TimeManager:images/play.png")) def renderLabel(self, painter): """Render the current timestamp on the map canvas""" if not self.showLabel or not self.model.hasLayers() or not self.dock.pushButtonToggleTime.isChecked(): return dt = self.model.getCurrentTimePosition() if dt is None: return labelString = self.labelOptions.getLabel(dt) # Determine placement of label given cardinal directions flags = 0 for direction, flag in ('N', Qt.AlignTop), ('S', Qt.AlignBottom), ('E', Qt.AlignRight), ('W', Qt.AlignLeft): if direction in self.labelOptions.placement: flags |= flag # Get canvas dimensions width = painter.device().width() height = painter.device().height() painter.setRenderHint(painter.Antialiasing, True) txt = QTextDocument() html = """<span style="background-color:%s; padding: 5px; font-size: %spx;"> <font face="%s" color="%s">%s</font> </span> """\ % (self.labelOptions.bgcolor, self.labelOptions.size, self.labelOptions.font, self.labelOptions.color, labelString) txt.setHtml(html) layout = txt.documentLayout() size = layout.documentSize() if flags & Qt.AlignRight: x = width - 5 - size.width() elif flags & Qt.AlignLeft: x = 5 else: x = width / 2 - size.width() / 2 if flags & Qt.AlignBottom: y = height - 5 - size.height() elif flags & Qt.AlignTop: y = 5 else: y = height / 2 - size.height() / 2 painter.translate(x, y) layout.draw(painter, QAbstractTextDocumentLayout.PaintContext()) painter.translate(-x, -y) # translate back def repaintRasters(self): rasters = self.model.getActiveRasters() list([x.layer.triggerRepaint() for x in rasters]) def repaintVectors(self): list([x.layer.triggerRepaint() for x in self.model.getActiveVectors()]) def repaintJoined(self): layerIdsToRefresh = qgs.getAllJoinedLayers(set([x.layer.id() for x in self.model.getActiveVectors()])) # info("to refresh {}".format(layerIdsToRefresh)) layersToRefresh = [qgs.getLayerFromId(x) for x in layerIdsToRefresh] list([x.triggerRepaint() for x in layersToRefresh])
class QmsServiceToolbox(QDockWidget, FORM_CLASS): def __init__(self, iface): QDockWidget.__init__(self, iface.mainWindow()) self.setupUi(self) self.newsFrame.setVisible(False) self.iface = iface self.search_threads = None # [] self.extent_renderer = RubberBandResultRenderer() self.cmbStatusFilter.addItem(self.tr('All'), STATUS_FILTER_ALL) self.cmbStatusFilter.addItem(self.tr('Valid'), STATUS_FILTER_ONLY_WORKS) self.cmbStatusFilter.currentIndexChanged.connect(self.start_search) if hasattr(self.txtSearch, 'setPlaceholderText'): self.txtSearch.setPlaceholderText(self.tr("Search string...")) self.delay_timer = QTimer(self) self.delay_timer.setSingleShot(True) self.delay_timer.setInterval(250) self.delay_timer.timeout.connect(self.start_search) self.txtSearch.textChanged.connect(self.delay_timer.start) self.btnFilterByExtent.toggled.connect(self.toggle_filter_button) self.one_process_work = QMutex() self.add_last_used_services() self.show_news() def show_news(self): client = Client() client.set_proxy(*QGISSettings.get_qgis_proxy()) qms_news = client.get_news() if qms_news is None: self.newsFrame.setVisible(False) return news = News(qms_news) if news.is_time_to_show(): self.newsLabel.setText(news.html) self.newsFrame.setVisible(True) else: self.newsFrame.setVisible(False) def toggle_filter_button(self, checked): self.txtSearch.setDisabled(checked) if checked: self.iface.mapCanvas().extentsChanged.connect(self.start_search) self.iface.mapCanvas().destinationCrsChanged.connect( self.start_search) self.start_search() else: self.iface.mapCanvas().extentsChanged.disconnect(self.start_search) self.iface.mapCanvas().destinationCrsChanged.disconnect( self.start_search) def start_search(self): search_text = None geom_filter = None # status filter status_filter = None sel_value = self.cmbStatusFilter.itemData( self.cmbStatusFilter.currentIndex()) if sel_value != STATUS_FILTER_ALL: status_filter = sel_value if not self.btnFilterByExtent.isChecked(): # text search search_text = unicode(self.txtSearch.text()) if not search_text: self.lstSearchResult.clear() # self.clearSearchResult() self.add_last_used_services() return else: # extent filter extent = self.iface.mapCanvas().extent() map_crs = getCanvasDestinationCrs(self.iface) if map_crs.postgisSrid() != 4326: crsDest = QgsCoordinateReferenceSystem(4326) # WGS 84 xform = QgsCoordinateTransform(map_crs, crsDest) extent = xform.transform(extent) geom_filter = extent.asWktPolygon() if self.search_threads: self.search_threads.data_downloaded.disconnect() self.search_threads.search_finished.disconnect() self.search_threads.stop() self.search_threads.wait() self.lstSearchResult.clear() # self.clearSearchResult() searcher = SearchThread(search_text, self.one_process_work, parent=self.iface.mainWindow(), geom_filter=geom_filter, status_filter=status_filter) searcher.data_downloaded.connect(self.show_result) searcher.error_occurred.connect(self.show_error) searcher.search_started.connect(self.search_started_process) searcher.search_finished.connect(self.search_finished_progress) self.search_threads = searcher searcher.start() def add_last_used_services(self): services = CachedServices().get_cached_services() if len(services) == 0: return self.lstSearchResult.insertItem(0, self.tr("Last used:")) # l = QLabel(self.tr("Last used:")) # l.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) # self.lSearchResult.addWidget(l) for attributes, image_qByteArray in services: custom_widget = QmsSearchResultItemWidget(attributes, image_qByteArray) new_item = QListWidgetItem(self.lstSearchResult) new_item.setSizeHint(custom_widget.sizeHint()) self.lstSearchResult.addItem(new_item) self.lstSearchResult.setItemWidget(new_item, custom_widget) # self.lSearchResult.addWidget(custom_widget) # w = QWidget() # w.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) # self.lSearchResult.addWidget(w) def search_started_process(self): self.lstSearchResult.clear() self.lstSearchResult.insertItem(0, self.tr('Searching...')) def search_finished_progress(self): self.lstSearchResult.takeItem(0) if self.lstSearchResult.count() == 0: new_widget = QLabel() new_widget.setTextFormat(Qt.RichText) new_widget.setOpenExternalLinks(True) new_widget.setWordWrap(True) new_widget.setText( u"<div align='center'> <strong>{}</strong> </div><div align='center' style='margin-top: 3px'> {} </div>" .format( self.tr(u"No results."), self. tr(u"You can add a service to become searchable. Start <a href='{}'>here</a>." ).format(u"https://qms.nextgis.com/create"), )) new_item = QListWidgetItem(self.lstSearchResult) new_item.setSizeHint(new_widget.sizeHint()) self.lstSearchResult.addItem(new_item) self.lstSearchResult.setItemWidget(new_item, new_widget) def show_result(self, geoservice, image_ba): if geoservice: custom_widget = QmsSearchResultItemWidget( geoservice, image_ba, extent_renderer=self.extent_renderer) new_item = QListWidgetItem(self.lstSearchResult) new_item.setSizeHint(custom_widget.sizeHint()) self.lstSearchResult.addItem(new_item) self.lstSearchResult.setItemWidget(new_item, custom_widget) else: new_item = QListWidgetItem() new_item.setText(self.tr('No results!')) new_item.setData(Qt.UserRole, None) self.lstSearchResult.addItem(new_item) self.lstSearchResult.update() def show_error(self, error_text): self.lstSearchResult.clear() new_widget = QLabel() new_widget.setTextFormat(Qt.RichText) new_widget.setOpenExternalLinks(True) new_widget.setWordWrap(True) new_widget.setText( u"<div align='center'> <strong>{}</strong> </div><div align='center' style='margin-top: 3px'> {} </div>" .format(self.tr('Error'), error_text)) new_item = QListWidgetItem(self.lstSearchResult) new_item.setSizeHint(new_widget.sizeHint()) self.lstSearchResult.addItem(new_item) self.lstSearchResult.setItemWidget(new_item, new_widget)
class TimeManagerGuiControl(QObject): """This class controls all plugin-related GUI elements. Emitted signals are defined here.""" showOptions = pyqtSignal() signalExportVideo = pyqtSignal(str, int, bool, bool, bool) toggleTime = pyqtSignal() toggleArchaeology = pyqtSignal() back = pyqtSignal() forward = pyqtSignal() play = pyqtSignal() signalCurrentTimeUpdated = pyqtSignal(QDateTime) signalSliderTimeChanged = pyqtSignal(float) signalTimeFrameType = pyqtSignal(str) signalTimeFrameSize = pyqtSignal(int) signalSaveOptions = pyqtSignal() signalArchDigitsSpecified = pyqtSignal(int) signalArchCancelled = pyqtSignal() def __init__(self, iface, model): """Initialize the GUI control""" QObject.__init__(self) self.iface = iface self.model = model self.optionsDialog = None self.path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'ui') # load the form self.dock = uic.loadUi(os.path.join(self.path, DOCK_WIDGET_FILE)) self.iface.addDockWidget(Qt.BottomDockWidgetArea, self.dock) self.dock.pushButtonExportVideo.setEnabled(False) # only enabled if there are managed layers self.dock.pushButtonOptions.clicked.connect(self.optionsClicked) self.dock.pushButtonExportVideo.clicked.connect(self.exportVideoClicked) self.dock.pushButtonToggleTime.clicked.connect(self.toggleTimeClicked) self.dock.pushButtonArchaeology.clicked.connect(self.archaeologyClicked) self.dock.pushButtonBack.clicked.connect(self.backClicked) self.dock.pushButtonForward.clicked.connect(self.forwardClicked) self.dock.pushButtonPlay.clicked.connect(self.playClicked) self.dock.dateTimeEditCurrentTime.dateTimeChanged.connect(self.currentTimeChangedDateText) # self.dock.horizontalTimeSlider.valueChanged.connect(self.currentTimeChangedSlider) self.sliderTimer = QTimer(self) self.sliderTimer.setInterval(250) self.sliderTimer.setSingleShot(True) self.sliderTimer.timeout.connect(self.currentTimeChangedSlider) self.dock.horizontalTimeSlider.valueChanged.connect(self.startTimer) self.dock.comboBoxTimeExtent.currentIndexChanged[str].connect(self.currentTimeFrameTypeChanged) self.dock.spinBoxTimeExtent.valueChanged.connect(self.currentTimeFrameSizeChanged) # this signal is responsible for rendering the label self.iface.mapCanvas().renderComplete.connect(self.renderLabel) # create shortcuts self.focusSC = QShortcut(QKeySequence("Ctrl+Space"), self.dock) self.focusSC.activated.connect(self.dock.horizontalTimeSlider.setFocus) # put default values self.dock.horizontalTimeSlider.setMinimum(conf.MIN_TIMESLIDER_DEFAULT) self.dock.horizontalTimeSlider.setMaximum(conf.MAX_TIMESLIDER_DEFAULT) self.dock.dateTimeEditCurrentTime.setMinimumDate(MIN_QDATE) self.showLabel = conf.DEFAULT_SHOW_LABEL self.exportEmpty = conf.DEFAULT_EXPORT_EMPTY self.labelOptions = TimestampLabelConfig(self.model) # placeholders for widgets that are added dynamically self.bcdateSpinBox = None # add to plugins toolbar try: self.action = QAction(QCoreApplication.translate("TimeManagerGuiControl", "Toggle visibility"), self.iface.mainWindow()) self.action.triggered.connect(self.toggleDock) self.iface.addPluginToMenu(QCoreApplication.translate("TimeManagerGuiControl", "&TimeManager"), self.action) except Exception as e: pass # OK for testing def startTimer(self): self.sliderTimer.start() def getLabelFormat(self): return self.labelOptions.fmt def getLabelFont(self): return self.labelOptions.font def getLabelSize(self): return self.labelOptions.size def getLabelColor(self): return self.labelOptions.color def getLabelBgColor(self): return self.labelOptions.bgcolor def getLabelPlacement(self): return self.labelOptions.placement def setLabelFormat(self, fmt): if not fmt: return self.labelOptions.fmt = fmt def setLabelFont(self, font): if not font: return self.labelOptions.font = font def setLabelSize(self, size): if not size: return self.labelOptions.size = size def setLabelColor(self, color): if not color: return self.labelOptions.color = color def setLabelBgColor(self, bgcolor): if not bgcolor: return self.labelOptions.bgcolor = bgcolor def setLabelPlacement(self, placement): if not placement: return self.labelOptions.placement = placement def toggleDock(self): self.dock.setVisible(not self.dock.isVisible()) def getOptionsDialog(self): return self.optionsDialog def showAnimationOptions(self): self.animationDialog = uic.loadUi(os.path.join(self.path, ANIMATION_WIDGET_FILE)) def selectFile(): self.animationDialog.lineEdit.setText(QFileDialog.getOpenFileName()) self.animationDialog.pushButton.clicked.connect(self.selectAnimationFolder) self.animationDialog.buttonBox.accepted.connect(self.sendAnimationOptions) self.animationDialog.show() def selectAnimationFolder(self): prev_directory = TimeManagerProjectHandler.plugin_setting(conf.LAST_ANIMATION_PATH_TAG) if prev_directory: self.animationDialog.lineEdit.setText(QFileDialog.getExistingDirectory(directory=prev_directory)) else: self.animationDialog.lineEdit.setText(QFileDialog.getExistingDirectory()) def sendAnimationOptions(self): path = self.animationDialog.lineEdit.text() if path == "": self.showAnimationOptions() TimeManagerProjectHandler.set_plugin_setting(conf.LAST_ANIMATION_PATH_TAG, path) delay_millis = self.animationDialog.spinBoxDelay.value() export_gif = self.animationDialog.radioAnimatedGif.isChecked() export_video = False # self.animationDialog.radioVideo.isChecked() do_clear = self.animationDialog.clearCheckBox.isChecked() self.signalExportVideo.emit(path, delay_millis, export_gif, export_video, do_clear) def showLabelOptions(self): options = self.labelOptions self.dialog = QDialog() lo = uic.loadUiType(os.path.join(self.path, LABEL_OPTIONS_WIDGET_FILE))[0] self.labelOptionsDialog = lo() self.labelOptionsDialog.setupUi(self.dialog) self.labelOptionsDialog.fontsize.setValue(options.size) self.labelOptionsDialog.time_format.setText(options.fmt) self.labelOptionsDialog.font.setCurrentFont(QFont(options.font)) self.labelOptionsDialog.placement.addItems(TimestampLabelConfig.PLACEMENTS) self.labelOptionsDialog.placement.setCurrentIndex(TimestampLabelConfig.PLACEMENTS.index(options.placement)) self.labelOptionsDialog.text_color.setColor(QColor(options.color)) self.labelOptionsDialog.bg_color.setColor(QColor(options.bgcolor)) self.labelOptionsDialog.buttonBox.accepted.connect(self.saveLabelOptions) self.dialog.show() def saveLabelOptions(self): self.labelOptions.font = self.labelOptionsDialog.font.currentFont().family() self.labelOptions.size = self.labelOptionsDialog.fontsize.value() self.labelOptions.bgcolor = self.labelOptionsDialog.bg_color.color().name() self.labelOptions.color = self.labelOptionsDialog.text_color.color().name() self.labelOptions.placement = self.labelOptionsDialog.placement.currentText() self.labelOptions.fmt = self.labelOptionsDialog.time_format.text() if self.labelOptionsDialog.radioButton_dt.isChecked(): self.labelOptions.type = "dt" if self.labelOptionsDialog.radioButton_beginning.isChecked(): self.labelOptions.type = "beginning" if self.labelOptionsDialog.radioButton_epoch.isChecked(): self.labelOptions.type = "epoch" def enableArchaeologyTextBox(self): self.dock.dateTimeEditCurrentTime.dateTimeChanged.connect(self.currentTimeChangedDateText) if self.bcdateSpinBox is None: self.bcdateSpinBox = self.createBCWidget(self.dock) self.bcdateSpinBox.editingFinished.connect(self.currentBCYearChanged) self.replaceWidget(self.dock.horizontalLayout, self.dock.dateTimeEditCurrentTime, self.bcdateSpinBox, 5) def getTimeWidget(self): if time_util.is_archaelogical(): return self.bcdateSpinBox else: return self.dock.dateTimeEditCurrentTime def currentBCYearChanged(self): val = self.bcdateSpinBox.text() try: bcdate_util.BCDate.from_str(val, strict_zeros=False) self.signalCurrentTimeUpdated.emit(val) except Exception as e: warn("Invalid bc date: {}".format(val)) # how to mark as such? return def disableArchaeologyTextBox(self): if self.bcdateSpinBox is None: return self.replaceWidget(self.dock.horizontalLayout, self.bcdateSpinBox, self.dock.dateTimeEditCurrentTime, 5) def createBCWidget(self, mainWidget): newWidget = QLineEdit(mainWidget) # QtGui.QSpinBox(mainWidget) # newWidget.setMinimum(-1000000) # newWidget.setValue(-1) newWidget.setText("0001 BC") return newWidget def replaceWidget(self, layout, oldWidget, newWidget, idx): """ Replaces oldWidget with newWidget at layout at index idx The way it is done, the widget is not destroyed and the connections to it remain """ layout.removeWidget(oldWidget) oldWidget.close() # I wonder if this has any memory leaks? </philosoraptor> layout.insertWidget(idx, newWidget) newWidget.show() layout.update() def optionsClicked(self): self.showOptions.emit() def exportVideoClicked(self): self.showAnimationOptions() def toggleTimeClicked(self): self.toggleTime.emit() def archaeologyClicked(self): self.toggleArchaeology.emit() def showArchOptions(self): self.archMenu = uic.loadUi(os.path.join(self.path, ARCH_WIDGET_FILE)) self.archMenu.buttonBox.accepted.connect(self.saveArchOptions) self.archMenu.buttonBox.rejected.connect(self.cancelArch) self.archMenu.show() def saveArchOptions(self): self.signalArchDigitsSpecified.emit(self.archMenu.numDigits.value()) def cancelArch(self): self.signalArchCancelled.emit() def backClicked(self): self.back.emit() def forwardClicked(self): self.forward.emit() def playClicked(self): if self.dock.pushButtonPlay.isChecked(): self.dock.pushButtonPlay.setIcon(QIcon("TimeManager:ui/images/pause.png")) else: self.dock.pushButtonPlay.setIcon(QIcon("TimeManager:ui/images/play.png")) self.play.emit() def currentTimeChangedSlider(self): sliderVal = self.dock.horizontalTimeSlider.value() try: pct = (sliderVal - self.dock.horizontalTimeSlider.minimum()) * 1.0 / ( self.dock.horizontalTimeSlider.maximum() - self.dock.horizontalTimeSlider.minimum()) except Exception as e: # slider is not properly initialized yet return if self.model.getActiveDelimitedText() and qgs.getVersion() < conf.MIN_DTEXT_FIXED: time.sleep(0.1) # hack to fix issue in qgis core with delimited text which was fixed in 2.9 self.signalSliderTimeChanged.emit(pct) def currentTimeChangedDateText(self, qdate): # info("changed time via text") self.signalCurrentTimeUpdated.emit(qdate) def currentTimeFrameTypeChanged(self, frameType): self.signalTimeFrameType.emit(frameType) def currentTimeFrameSizeChanged(self, frameSize): if frameSize < 1: # time frame size = 0 is meaningless self.dock.spinBoxTimeExtent.setValue(1) return self.signalTimeFrameSize.emit(frameSize) def unload(self): """Unload the plugin""" self.iface.removeDockWidget(self.dock) self.iface.removePluginMenu("TimeManager", self.action) def setWindowTitle(self, title): self.dock.setWindowTitle(title) def showOptionsDialog(self, layerList, animationFrameLength, playBackwards=False, loopAnimation=False): """Show the optionsDialog and populate it with settings from timeLayerManager""" # load the form self.optionsDialog = uic.loadUi(os.path.join(self.path, OPTIONS_WIDGET_FILE)) # restore settings from layerList: for layer in layerList: settings = layer_settings.getSettingsFromLayer(layer) layer_settings.addSettingsToRow(settings, self.optionsDialog.tableWidget) self.optionsDialog.tableWidget.resizeColumnsToContents() # restore animation options self.optionsDialog.spinBoxFrameLength.setValue(animationFrameLength) self.optionsDialog.checkBoxBackwards.setChecked(playBackwards) self.optionsDialog.checkBoxLabel.setChecked(self.showLabel) self.optionsDialog.checkBoxDontExportEmpty.setChecked(not self.exportEmpty) self.optionsDialog.checkBoxLoop.setChecked(loopAnimation) self.optionsDialog.show_label_options_button.clicked.connect(self.showLabelOptions) self.optionsDialog.checkBoxLabel.stateChanged.connect(self.showOrHideLabelOptions) self.optionsDialog.textBrowser.setHtml(QCoreApplication.translate('TimeManager', """\ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html> <head> <meta name="qrichtext" content="1"/> <style> li.mono { font-family: Consolas, Courier New, Courier, monospace; } </style> </head> <body> <h1>Time Manager</h1> <p>Time Manager filters your layers and displays only layers and features that match the specified time frame. Time Manager supports vector layers and raster layers (including WMS with TIME dimension).</p> <p>Timestamps have to be in one of the following formats:</p> <ul> <li class="mono">%Y-%m-%d %H:%M:%S.%f</li> <li class="mono">%Y-%m-%d %H:%M:%S</li> <li class="mono">%Y-%m-%d %H:%M</li> <li class="mono">%Y-%m-%dT%H:%M:%S</li> <li class="mono">%Y-%m-%dT%H:%M:%SZ</li> <li class="mono">%Y-%m-%dT%H:%M</li> <li class="mono">%Y-%m-%dT%H:%MZ</li> <li class="mono">%Y-%m-%d</li> <li class="mono">%Y/%m/%d %H:%M:%S.%f</li> <li class="mono">%Y/%m/%d %H:%M:%S</li> <li class="mono">%Y/%m/%d %H:%M</li> <li class="mono">%Y/%m/%d</li> <li class="mono">%H:%M:%S</li> <li class="mono">%H:%M:%S.%f</li> <li class="mono">%Y.%m.%d %H:%M:%S.%f</li> <li class="mono">%Y.%m.%d %H:%M:%S</li> <li class="mono">%Y.%m.%d %H:%M</li> <li class="mono">%Y.%m.%d</li> <li class="mono">%Y%m%d%H%M%SED</li> <li>Integer timestamp in seconds after or before the epoch (1970-1-1)</li> </ul> <p>The layer list contains all layers managed by Time Manager. To add a vector layer, press [Add layer]. To add a raster layer, press [Add raster]. If you want to remove a layer from the list, select it and press [Remove layer].</p> <p>Below the layer list, you'll find the following <b>animation options</b>:</p> <p><b>Show frame for x milliseconds</b>... allows you to adjust for how long a frame will be visible during the animation</p> <p><b>Play animation backwards</b>... if checked, the animation will run in reverse direction</p> <p><b>Display frame start time on map</b>... if checked, displays the start time of the visible frame in the lower right corner of the map</p> <h2>Add Layer dialog</h2> <p>Here, you are asked to select the layer that should be added and specify the columns containing start and (optionally) end time.</p> <p>The <b>offset</b> option allows you to further time the appearance of features. If you specify an offset of -1, the features will appear one second later than they would by default.</p> <h2>Dock widget</h2> <p>The dock was designed to attach to the bottom of the QGIS main window. It offers the following tools:</p> <ul> <li><img src="images/power_on.png" alt="power"/> ... On/Off switch, allows you to turn Time Manager's functionality on/off with the click of only one button</li> <li><span class="hidden">[Settings]</span><input type="button" value="Settings"/> ... opens the Settings dialog where you can manage your spatio-temporal layers</li> <li><span class="hidden">[Export Video]</span><input type="button" value="Export Video"/> ... exports an image series based on current settings (This button is only enabled if there are layers registered in Time Manager "Settings")</li> <li><b>Time frame start: <span class="hidden">[2000-01-01 00:00:00]</span></b><input type="text" value="2000-01-01 00:00:00"/> ... shows the start time of the currently active frame. Allows you to precisely specify your desired analysis time.</li> <li><b>Time frame size: </b><input type="text" value="1"/><span class="hidden">[x]</span><select><option value="days">days</option></select> ... allow you to choose the size of the time frame</li> <li><img src="images/back.png" alt="back"/> ... go to the previous time frame</li> <li><img src="images/forward.png" alt="forward"/> ... go to the next time frame</li> <li><b>Slider</b> ... shows the position of current frame relative to the whole dataset and allows you to seamlessly scroll through the dataset</li> <li><img src="images/play.png" alt="play"/> ... start an automatic animation based on your current settings</li> </ul> </body> </html>""")) # show dialog self.showOrHideLabelOptions() self.optionsDialog.show() # create raster and vector dialogs self.vectorDialog = VectorLayerDialog(self.iface, os.path.join(self.path, ADD_VECTOR_LAYER_WIDGET_FILE), self.optionsDialog.tableWidget) self.rasterDialog = RasterLayerDialog(self.iface, os.path.join(self.path, ADD_RASTER_LAYER_WIDGET_FILE), self.optionsDialog.tableWidget) # establish connections self.optionsDialog.pushButtonAddVector.clicked.connect(self.vectorDialog.show) self.optionsDialog.pushButtonAddRaster.clicked.connect(self.rasterDialog.show) self.optionsDialog.pushButtonRemove.clicked.connect(self.removeLayer) self.optionsDialog.buttonBox.accepted.connect(self.saveOptions) # self.optionsDialog.buttonBox.helpRequested.connect(self.showHelp) def showOrHideLabelOptions(self): self.optionsDialog.show_label_options_button.setEnabled( self.optionsDialog.checkBoxLabel.isChecked()) def saveOptions(self): """Save the options from optionsDialog to timeLayerManager""" self.signalSaveOptions.emit() def removeLayer(self): """Remove currently selected layer (= row) from options""" currentRow = self.optionsDialog.tableWidget.currentRow() try: layerName = self.optionsDialog.tableWidget.item(currentRow, 0).text() except AttributeError: # if no row is selected return if QMessageBox.question(self.optionsDialog, QCoreApplication.translate("TimeManagerGuiControl", "Remove Layer"), QCoreApplication.translate("TimeManagerGuiControl", "Do you really want to remove layer {}?").format(layerName), QMessageBox.Ok, QMessageBox.Cancel) == QMessageBox.Ok: self.optionsDialog.tableWidget.removeRow(self.optionsDialog.tableWidget.currentRow()) def disableAnimationExport(self): """Disable the animation export button""" self.dock.pushButtonExportVideo.setEnabled(False) def enableAnimationExport(self): """Enable animation export button""" self.dock.pushButtonExportVideo.setEnabled(True) def refreshMapCanvas(self, sender=None): """Refresh the map canvas""" # QMessageBox.information(self.iface.mainWindow(),'Test Output','Refresh!\n'+str(sender)) self.iface.mapCanvas().refresh() def setTimeFrameSize(self, frameSize): """Set spinBoxTimeExtent to given frameSize""" self.dock.spinBoxTimeExtent.setValue(frameSize) def setTimeFrameType(self, frameType): """Set comboBoxTimeExtent to given frameType""" i = self.dock.comboBoxTimeExtent.findText(frameType) self.dock.comboBoxTimeExtent.setCurrentIndex(i) def setActive(self, isActive): """Toggle pushButtonToggleTime""" self.dock.pushButtonToggleTime.setChecked(isActive) def setArchaeologyPressed(self, isActive): """Toggle pushButtonArchaeology""" self.dock.pushButtonArchaeology.setChecked(isActive) def addActionShowSettings(self, action): """Add action to pushButttonOptions""" self.dock.pushButtonOptions.addAction(action) def turnPlayButtonOff(self): """Turn pushButtonPlay off""" if self.dock.pushButtonPlay.isChecked(): self.dock.pushButtonPlay.toggle() self.dock.pushButtonPlay.setIcon(QIcon("TimeManager:ui/images/play.png")) def renderLabel(self, painter): """Render the current timestamp on the map canvas""" if not self.showLabel or not self.model.hasLayers() or not self.dock.pushButtonToggleTime.isChecked(): return dt = self.model.getCurrentTimePosition() if dt is None: return labelString = self.labelOptions.getLabel(dt) # Determine placement of label given cardinal directions flags = 0 for direction, flag in ('N', Qt.AlignTop), ('S', Qt.AlignBottom), ('E', Qt.AlignRight), ('W', Qt.AlignLeft): if direction in self.labelOptions.placement: flags |= flag # Get canvas dimensions pixelRatio = painter.device().devicePixelRatio() width = painter.device().width() / pixelRatio height = painter.device().height() / pixelRatio painter.setRenderHint(painter.Antialiasing, True) txt = QTextDocument() html = """<span style="background-color:%s; padding: 5px; font-size: %spx;"> <font face="%s" color="%s"> %s</font> </span> """\ % (self.labelOptions.bgcolor, self.labelOptions.size, self.labelOptions.font, self.labelOptions.color, labelString) txt.setHtml(html) layout = txt.documentLayout() size = layout.documentSize() if flags & Qt.AlignRight: x = width - 5 - size.width() elif flags & Qt.AlignLeft: x = 5 else: x = width / 2 - size.width() / 2 if flags & Qt.AlignBottom: y = height - 5 - size.height() elif flags & Qt.AlignTop: y = 5 else: y = height / 2 - size.height() / 2 painter.translate(x, y) layout.draw(painter, QAbstractTextDocumentLayout.PaintContext()) painter.translate(-x, -y) # translate back def repaintRasters(self): rasters = self.model.getActiveRasters() list([x.layer.triggerRepaint() for x in rasters]) def repaintVectors(self): list([x.layer.triggerRepaint() for x in self.model.getActiveVectors()]) def repaintJoined(self): layerIdsToRefresh = qgs.getAllJoinedLayers(set([x.layer.id() for x in self.model.getActiveVectors()])) # info("to refresh {}".format(layerIdsToRefresh)) layersToRefresh = [qgs.getLayerFromId(x) for x in layerIdsToRefresh] list([x.triggerRepaint() for x in layersToRefresh])
class OpenlayersController(QObject): """ Helper class that deals with QWebPage. The object lives in GUI thread, its request() slot is asynchronously called from worker thread. See https://github.com/wonder-sk/qgis-mtr-example-plugin for basic example 1. Load Web Page with OpenLayers map 2. Update OL map extend according to QGIS canvas extent """ # signal that reports to the worker thread that the image is ready finished = pyqtSignal() def __init__(self, parent, context, webPage, layerType): QObject.__init__(self, parent) debug("OpenlayersController.__init__", 3) self.context = context self.layerType = layerType self.img = QImage() self.page = webPage self.page.loadFinished.connect(self.pageLoaded) # initial size for map self.page.setViewportSize(QSize(1, 1)) self.timerMapReady = QTimer() self.timerMapReady.setSingleShot(True) self.timerMapReady.setInterval(20) self.timerMapReady.timeout.connect(self.checkMapReady) self.timer = QTimer() self.timer.setInterval(100) self.timer.timeout.connect(self.checkMapUpdate) self.timerMax = QTimer() self.timerMax.setSingleShot(True) # TODO: different timeouts for map types self.timerMax.setInterval(2500) self.timerMax.timeout.connect(self.mapTimeout) @pyqtSlot() def request(self): debug("[GUI THREAD] Processing request", 3) self.cancelled = False if not self.page.loaded: self.init_page() else: self.setup_map() def init_page(self): url = self.layerType.html_url() debug("page file: %s" % url) self.page.mainFrame().load(QUrl(url)) # wait for page to finish loading debug("OpenlayersWorker waiting for page to load", 3) def pageLoaded(self): debug("[GUI THREAD] pageLoaded", 3) if self.cancelled: self.emitErrorImage() return # wait until OpenLayers map is ready self.checkMapReady() def checkMapReady(self): debug("[GUI THREAD] checkMapReady", 3) if self.page.mainFrame().evaluateJavaScript("map != undefined"): # map ready self.page.loaded = True self.setup_map() else: # wait for map self.timerMapReady.start() def setup_map(self): rendererContext = self.context # FIXME: self.mapSettings.outputDpi() self.outputDpi = rendererContext.painter().device().logicalDpiX() debug(" extent: %s" % rendererContext.extent().toString(), 3) debug( " center: %lf, %lf" % (rendererContext.extent().center().x(), rendererContext.extent().center().y()), 3) debug( " size: %d, %d" % (rendererContext.painter().viewport().size().width(), rendererContext.painter().viewport().size().height()), 3) debug( " logicalDpiX: %d" % rendererContext.painter().device().logicalDpiX(), 3) debug(" outputDpi: %lf" % self.outputDpi) debug( " mapUnitsPerPixel: %f" % rendererContext.mapToPixel().mapUnitsPerPixel(), 3) # debug(" rasterScaleFactor: %s" % str(rendererContext. # rasterScaleFactor()), 3) # debug(" outputSize: %d, %d" % (self.iface.mapCanvas().mapRenderer(). # outputSize().width(), # self.iface.mapCanvas().mapRenderer(). # outputSize().height()), 3) # debug(" scale: %lf" % self.iface.mapCanvas().mapRenderer().scale(), # 3) # # if (self.page.lastExtent == rendererContext.extent() # and self.page.lastViewPortSize == rendererContext.painter(). # viewport().size() # and self.page.lastLogicalDpi == rendererContext.painter(). # device().logicalDpiX() # and self.page.lastOutputDpi == self.outputDpi # and self.page.lastMapUnitsPerPixel == rendererContext. # mapToPixel().mapUnitsPerPixel()): # self.renderMap() # self.finished.emit() # return self.targetSize = rendererContext.painter().viewport().size() if rendererContext.painter().device().logicalDpiX() != int( self.outputDpi): # use screen dpi for printing sizeFact = self.outputDpi / 25.4 / rendererContext.mapToPixel( ).mapUnitsPerPixel() self.targetSize.setWidth(rendererContext.extent().width() * sizeFact) self.targetSize.setHeight(rendererContext.extent().height() * sizeFact) debug( " targetSize: %d, %d" % (self.targetSize.width(), self.targetSize.height()), 3) # find matching OL resolution qgisRes = rendererContext.extent().width() / self.targetSize.width() olRes = None for res in self.page.resolutions(): if qgisRes >= res: olRes = res break if olRes is None: debug("No matching OL resolution found (QGIS resolution: %f)" % qgisRes) self.emitErrorImage() return # adjust OpenLayers viewport to match QGIS extent olWidth = rendererContext.extent().width() / olRes olHeight = rendererContext.extent().height() / olRes debug( " adjust viewport: %f -> %f: %f x %f" % (qgisRes, olRes, olWidth, olHeight), 3) olSize = QSize(int(olWidth), int(olHeight)) self.page.setViewportSize(olSize) self.page.mainFrame().evaluateJavaScript("map.updateSize();") self.img = QImage(olSize, QImage.Format_ARGB32_Premultiplied) self.page.extent = rendererContext.extent() debug( "map.zoomToExtent (%f, %f, %f, %f)" % (self.page.extent.xMinimum(), self.page.extent.yMinimum(), self.page.extent.xMaximum(), self.page.extent.yMaximum()), 3) self.page.mainFrame().evaluateJavaScript( "map.zoomToExtent(new OpenLayers.Bounds(%f, %f, %f, %f), true);" % (self.page.extent.xMinimum(), self.page.extent.yMinimum(), self.page.extent.xMaximum(), self.page.extent.yMaximum())) olextent = self.page.mainFrame().evaluateJavaScript("map.getExtent();") debug("Resulting OL extent: %s" % olextent, 3) if olextent is None or not hasattr(olextent, '__getitem__'): debug("map.zoomToExtent failed") # map.setCenter and other operations throw "undefined[0]: # TypeError: 'null' is not an object" on first page load # We ignore that and render the initial map with wrong extents # self.emitErrorImage() # return else: reloffset = abs((self.page.extent.yMaximum() - olextent["top"]) / self.page.extent.xMinimum()) debug("relative offset yMaximum %f" % reloffset, 3) if reloffset > 0.1: debug("OL extent shift failure: %s" % reloffset) self.emitErrorImage() return self.mapFinished = False self.timer.start() self.timerMax.start() def checkMapUpdate(self): if self.layerType.emitsLoadEnd: # wait for OpenLayers to finish loading loadEndOL = self.page.mainFrame().evaluateJavaScript("loadEnd") debug( "waiting for loadEnd: renderingStopped=%r loadEndOL=%r" % (self.context.renderingStopped(), loadEndOL), 4) if loadEndOL is not None: self.mapFinished = loadEndOL else: debug("OpenlayersLayer Warning: Could not get loadEnd") if self.mapFinished: self.timerMax.stop() self.timer.stop() self.renderMap() self.finished.emit() def renderMap(self): rendererContext = self.context if rendererContext.painter().device().logicalDpiX() != int( self.outputDpi): printScale = 25.4 / self.outputDpi # OL DPI to printer pixels rendererContext.painter().scale(printScale, printScale) # render OpenLayers to image painter = QPainter(self.img) self.page.mainFrame().render(painter) painter.end() if self.img.size() != self.targetSize: targetWidth = self.targetSize.width() targetHeight = self.targetSize.height() # scale using QImage for better quality debug( " scale image: %i x %i -> %i x %i" % (self.img.width(), self.img.height(), targetWidth, targetHeight), 3) self.img = self.img.scaled(targetWidth, targetHeight, Qt.KeepAspectRatio, Qt.SmoothTransformation) # save current state self.page.lastExtent = rendererContext.extent() self.page.lastViewPortSize = rendererContext.painter().viewport().size( ) self.page.lastLogicalDpi = rendererContext.painter().device( ).logicalDpiX() self.page.lastOutputDpi = self.outputDpi self.page.lastMapUnitsPerPixel = rendererContext.mapToPixel( ).mapUnitsPerPixel() def mapTimeout(self): debug("mapTimeout reached") self.timer.stop() # if not self.layerType.emitsLoadEnd: self.renderMap() self.finished.emit() def emitErrorImage(self): self.img = QImage() self.targetSize = self.img.size self.finished.emit()
class GESync: """Integracja QGIS'a z Google Earth Pro.""" def __init__(self): self.screen_scale = 1 # Wartość skalowania rozdzielczości ekranowej self.q_id = None # Id procesu QGIS'a self.ge_id = None # Id procesu Google Earth Pro self.q_hwnd = None # Handler okna QGIS'a self.ge_hwnd = None # Handler okna Google Earth Pro self.bmp_hwnd = None # Handler subokna Google Earth Pro z widokiem samej mapy self.bytes = int() # Rozmiar aktualnego pliku jpg self.is_ge = False # Czy Google Earth Pro jest uruchomiony? self.is_on = False # Czy warstwa 'Google Earth Pro' jest włączona? self.tmp_num = 0 # Numer pliku tymczasowego self.extent = None # Zasięg geoprzestrzenny aktualnego widoku mapy self.t_void = False # Blokada stopera self.player = False # Czy w danym momencie uruchomiony jest player sekwencji? self.loaded = False # Czy widok mapy się załadował? self.ge_layer = QgsProject.instance().mapLayersByName('Google Earth Pro')[0] # Referencja warstwy 'Google Earth Pro' self.ge_legend = QgsProject.instance().layerTreeRoot().findLayer(self.ge_layer.id()) # Referencja warstwy w legendzie self.bmp_w = int() # Szerokość bmp self.bmp_h = int() # Wysokość bmp self.jpg_file = "" # Ścieżka do pliku jpg self.get_handlers() iface.mapCanvas().extentsChanged.connect(self.extent_changed) def extent_changed(self): """Zmiana zakresu geoprzestrzennego widoku mapy.""" # Wyjście z funkcji, jeśli stoper obecnie pracuje: if self.t_void: return self.t_void = True if self.is_on: self.extent = iface.mapCanvas().extent() self.loaded = False # print(f"loaded: {self.loaded}") # Wyłączenie warstwy z maską powiatu (QGIS zawiesza się przy częstym zoomowaniu z tą włączoną warstwą): # QgsProject.instance().layerTreeRoot().findLayer(QgsProject.instance().mapLayersByName("powiaty_mask")[0].id()).setItemVisibilityChecked(False) self.timer = QTimer() self.timer.setInterval(200) self.timer.timeout.connect(self.check_extent) self.timer.start() # Odpalenie stopera def check_extent(self): """Sprawdzenie, czy widok mapy przestał się zmieniać.""" if self.extent != iface.mapCanvas().extent(): # Zmienił się self.extent = iface.mapCanvas().extent() else: # Kasowanie licznika: self.timer.stop() self.timer.deleteLater() self.t_void = False self.extent = iface.mapCanvas().extent() # self.loaded = True # print(f"loaded: {self.loaded}") # Włączenie warstwy z maską powiatu: # QgsProject.instance().layerTreeRoot().findLayer(QgsProject.instance().mapLayersByName("powiaty_mask")[0].id()).setItemVisibilityChecked(True) if self.is_on: self.ge_sync() def visible_changed(self, value): """Włączenie / wyłączenie warstwy 'Google Earth Pro'.""" # print(f"[visible_changed]") if value: # Włączono warstwę self.is_on = True if self.extent != iface.mapCanvas().extent(): self.extent = iface.mapCanvas().extent() self.ge_sync() if self.player or not self.loaded: self.ge_grabber() else: # Wyłączono warstwę self.is_on = False def get_handlers(self): """Ustalenie id procesów i handlerów okien QGIS'a i Google Earth Pro (jeśli jest uruchomiony).""" # Utworzenie listy uruchomionych procesów: processes = GetObject('winmgmts:').InstancesOf('Win32_Process') process_list = [(p.Properties_("ProcessID").Value, p.Properties_("Name").Value) for p in processes] ge_flag = False for p in process_list: if p[1] == "qgis-bin-g7.exe" or p[1] == "qgis-bin.exe" or p[1] == "qgis-ltr-bin.exe" or p[1] == "qgis-ltr-bin-g7.exe": self.q_id = p[0] # print(f"qgis: {p[1]}") elif p[1] == "googleearth.exe": ge_flag = True self.is_ge = True self.ge_id = p[0] # print(f"is_ge: {self.is_ge}, ge_id: {self.ge_id}") if not ge_flag: # Google Earth Pro nie jest uruchomiony self.ge_id = None self.is_ge = False # Pętla przeszukująca okna uruchomionych programów: win32gui.EnumWindows(self._enum_callback, None) def _enum_callback(self, hwnd, extras): """Ustalenie handlerów dla QGIS i Google Earth Pro.""" # Wyszukanie handlera QGIS'a: # if win32process.GetWindowThreadProcessId(hwnd)[1] == self.q_id: w_title = win32gui.GetWindowText(hwnd) # print(f"w_title: {w_title}") if w_title.find("| MOEK_Editor") != -1: # W nazwie otwartego pliku .qgz musi być fraza "*MOEK_editor" self.q_hwnd = hwnd # print(f"self.q_hwnd: {self.q_hwnd}") # Wyszukanie handlera Google Earth Pro: if self.is_ge and w_title == "Google Earth Pro": self.ge_hwnd = hwnd # print(f"self.ge_hwnd: {self.ge_hwnd}") # Wyszukanie handlera subokna Google Earth Pro z obrazem mapy: self.child = 0 try: win32gui.EnumChildWindows(self.ge_hwnd, self._enum_children, None) print("Handler subokna Google Earth Pro został ustalony.") except: pass def _enum_children(self, hwnd, extras): """Ustalenie handlera subokna Google Earth Pro z obrazem mapy.""" # print(f"child: {self.child}") self.child += 1 rect = win32gui.GetWindowRect(hwnd) # print(f"ge_width: {rect[2] - rect[0]}, ge_height: {rect[3] - rect[1]}") if self.child == 12: self.bmp_hwnd = hwnd def ge_sync(self): """Wyświetlenie w Google Earth Pro obszaru mapy z QGIS'a.""" # print("[ge_sync]") if not self.is_ge: # print(f"2. q2ge") self.q2ge() self.get_handlers() return # Sprawdzenie, czy Google Earth Pro jeszcze działa: try: win32gui.GetClientRect(self.bmp_hwnd) except: self.is_ge = False return # print(f"3. q2ge") self.q2ge() self.ge_grabber() def q2ge(self, back=True, player=False): """Przejście w Google Earth Pro do widoku mapy z QGIS'a.""" # print(f"[q2ge]") if not self.is_ge: self.get_handlers() canvas = iface.mapCanvas() crs_src = canvas.mapSettings().destinationCrs() # PL1992 crs_dest = QgsCoordinateReferenceSystem(4326) # WGS84 xform = QgsCoordinateTransform(crs_src, crs_dest, QgsProject.instance()) # Transformacja między układami if not self.extent or not back or player: # print("extent changed") self.loaded = False self.extent = iface.mapCanvas().extent() # Współrzędne rogów widoku mapy: x1 = self.extent.xMinimum() x2 = self.extent.xMaximum() y1 = self.extent.yMinimum() y2 = self.extent.yMaximum() # Wyznaczenie punktu centralnego: x = (x1 + x2) / 2 y = (y1 + y2) / 2 proj = QgsPointXY(x, y) # Punkt centralny w PL1992 # Transformacja punktu centralnego do WGS84: geo = xform.transform(QgsPointXY(proj.x(), proj.y())) # Współrzędne geograficzne punktu centralnego: lon = geo.x() lat = geo.y() # Ustalenie kąta obrotu mapy w GE: rot = -3.85 + (lon-14.11677234)*7.85/10.02872526 # Ustalenie wysokości kamery w GE: rng = canvas.scale() * (canvas.width()/100) * (0.022875 / self.screen_scale) TEMP_PATH = tempfile.gettempdir() # Ścieżka do folderu TEMP # Utworzenie pliku kml: kml = codecs.open(TEMP_PATH + '/moek.kml', 'w', encoding='utf-8') kml.write('<?xml version="1.0" encoding="UTF-8"?>\n') kml.write('<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom">\n') kml.write(' <Document>\n') kml.write(' <LookAt>\n') k_txt = f' <longitude>{lon}</longitude>\n' kml.write(k_txt) k_txt = f' <latitude>{lat}</latitude>\n' kml.write(k_txt) kml.write(' <altitude>0</altitude>\n') k_txt = f' <heading>{rot}</heading>\n' kml.write(k_txt) kml.write(' <tilt>0</tilt>\n') k_txt = f' <range>{rng}</range>\n' kml.write(k_txt) kml.write(' <gx:altitudeMode>relativeToGround</gx:altitudeMode>\n') kml.write(' </LookAt>\n') kml.write(' </Document>\n') kml.write('</kml>\n') kml.close() # Włączenie dla QGIS'a funkcji always on top: if back and self.is_ge: # print(f"qgis on top: True") try: win32gui.SetWindowPos(self.q_hwnd, win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE | win32con.SWP_NOSIZE | win32con.SWP_SHOWWINDOW) except: print(f"q_hwnd ({self.q_hwnd}) exception!") pass # Odpalenie pliku w Google Earth Pro: os.startfile(TEMP_PATH + '/moek.kml') if back and self.is_ge: QTimer.singleShot(1000, self.on_top_off) def on_top_off(self): """Wyłączenie dla QGIS'a funkcji always on top.""" # print(f"[on_top_off]") try: win32gui.SetWindowPos(self.q_hwnd, win32con.HWND_NOTOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE | win32con.SWP_NOSIZE | win32con.SWP_SHOWWINDOW) win32gui.SetForegroundWindow(self.q_hwnd) # print(f"qgis on top: False") except: print(f"q_hwnd ({self.q_hwnd}) exception!") self.get_handlers() def ge_grabber(self): """Główna funkcja przechwytywania obrazu z Google Earth Pro.""" # Ekranowe wymiary obrazka do przechwycenia: # print("[ge_grabber]") try: l,t,r,b = win32gui.GetClientRect(self.bmp_hwnd) except: print("ge_grabber exception!") self.get_handlers() self.ge_sync() return self.bmp_w = int((r - l) / self.screen_scale) self.bmp_h = int((b - t) / self.screen_scale) # print(f"bmp_w: {self.bmp_w}, bmp_h: {self.bmp_h}") self.tmp_num = 0 self.bytes = 0 # Przechwycenie obrazu i określenie ile waży zapisany jpg: tmp_bytes = self.create_jpg() # Tworzenie obrazów w pętli, aż do momentu braku zmian w rozmiarze pliku: while self.bytes != tmp_bytes: if self.tmp_num == 10: break self.tmp_num += 1 self.bytes = tmp_bytes time.sleep(0.2) tmp_bytes = self.create_jpg() self.create_jpg() # Ostateczne zapisanie pliku self.wld_creator() # Utworzenie pliku z georeferencjami self.layer_update() # Wczytanie jpg'a do warstwy self.loaded = True # print(f"loaded: {self.loaded}") # Wyłączenie dla QGIS'a funkcji always on top: self.on_top_off() def create_jpg(self): """Przechwycenie obrazu z Google Earth Pro i zapisanie go do .jpg.""" # print(f"[create_jpg]") dc = win32gui.GetDC(self.bmp_hwnd) hdc = win32ui.CreateDCFromHandle(dc) new_dc = hdc.CreateCompatibleDC() new_bmp = win32ui.CreateBitmap() new_bmp.CreateCompatibleBitmap(hdc, self.bmp_w, self.bmp_h) new_dc.SelectObject(new_bmp) new_dc.BitBlt((0,0),(self.bmp_w, self.bmp_h) , hdc, (0,0), win32con.SRCCOPY) bmp_bits = new_bmp.GetBitmapBits(True) img = Image.frombytes('RGB', (self.bmp_w, self.bmp_h), bmp_bits, 'raw', 'BGRX') self.jpg_file = TEMP_PATH + "\\ge.jpg" img.save(self.jpg_file, "JPEG", quality=75, optimize=False, progressive=False) win32gui.DeleteObject(new_bmp.GetHandle()) new_dc.DeleteDC() hdc.DeleteDC() win32gui.ReleaseDC(self.bmp_hwnd, dc) time.sleep(0.2) return os.stat(self.jpg_file).st_size def wld_creator(self): """Tworzenie pliku georeferencyjnego dla jpg'a.""" # Współrzędne rogów widoku mapy [m]: x1 = self.extent.xMinimum() x2 = self.extent.xMaximum() y1 = self.extent.yMinimum() y2 = self.extent.yMaximum() # Wymiary geoprzestrzenne mapy [m]: width = x2 - x1 height = y2 - y1 # Wymiary ekranowe mapy [px]: cnv_w = iface.mapCanvas().width() cnv_h = iface.mapCanvas().height() # Skalowanie bmp do szerokości ekranowej mapy: bmp_h_scaled = (cnv_w * self.bmp_h) / self.bmp_w # Wysokość geoprzestrzenna zeskalowanej bmp: height_scaled = (bmp_h_scaled * height) / cnv_h # Różnica wysokości geoprzestrzennej mapy i przeskalowanej bmp: height_diff = height_scaled - height # Współrzędna y2 przeskalowanej bmp: y2_bmp = y2 + (height_diff / 2) # Utworzenie pliku wld: wld = codecs.open(TEMP_PATH + '\\ge.wld', 'w', encoding='utf-8') wld.write(f'{width / self.bmp_w}\n') wld.write('0\n') wld.write('0\n') wld.write(f'{-height_scaled / self.bmp_h}\n') wld.write(f'{x1}\n') wld.write(f'{y2_bmp}\n') wld.close() def layer_update(self): """Aktualizacja warstwy Google Earth Pro.""" time.sleep(0.2) # Poczekanie, aż .jpg zapisze się na dysku iface.mainWindow().blockSignals(True) # Żeby QGIS nie monitował o braku crs'a ge_layer = QgsProject.instance().mapLayersByName('Google Earth Pro')[0] data_source = TEMP_PATH + "\\ge.jpg" base_name = ge_layer.name() provider = ge_layer.providerType() options = ge_layer.dataProvider().ProviderOptions() ge_layer.setDataSource(data_source, base_name, provider, options) ge_layer.setCrs(QgsCoordinateReferenceSystem(2180, QgsCoordinateReferenceSystem.EpsgCrsId)) ge_layer.reload() iface.mainWindow().blockSignals(False) iface.actionDraw().trigger() iface.mapCanvas().refresh()