예제 #1
0
파일: gps.py 프로젝트: AnnijaViktorija/Roam
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)
예제 #3
0
    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)
예제 #4
0
    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
    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
예제 #7
0
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 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 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))
예제 #11
0
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()
예제 #13
0
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
        )
예제 #15
0
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()
예제 #16
0
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()
예제 #17
0
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
예제 #18
0
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)
예제 #19
0
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()
예제 #20
0
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
예제 #21
0
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])
예제 #23
0
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)
예제 #24
0
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">&nbsp;%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()
예제 #26
0
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()