Example #1
0
    def _draw_rubberband(self, geometry, colour, width):
        """Draw a rubber band on the canvas.

        .. versionadded: 2.2.0

        :param geometry: Extent that the rubber band should be drawn for.
        :type geometry: QgsGeometry

        :param colour: Colour for the rubber band.
        :type colour: QColor

        :param width: The width for the rubber band pen stroke.
        :type width: int

        :returns: Rubber band that should be set to the extent.
        :rtype: QgsRubberBand
        """
        # noinspection PyArgumentList
        rubber_band = QgsRubberBand(self._map_canvas,
                                    geometryType=QGis.Polygon)
        rubber_band.setBrushStyle(Qt.NoBrush)
        rubber_band.setColor(colour)
        rubber_band.setWidth(width)
        rubber_band.setToGeometry(geometry, None)
        return rubber_band
Example #2
0
    def _draw_rubberband(self, geometry, colour, width):
        """Draw a rubber band on the canvas.

        .. versionadded: 2.2.0

        :param geometry: Extent that the rubber band should be drawn for.
        :type geometry: QgsGeometry

        :param colour: Colour for the rubber band.
        :type colour: QColor

        :param width: The width for the rubber band pen stroke.
        :type width: int

        :returns: Rubber band that should be set to the extent.
        :rtype: QgsRubberBand
        """
        # noinspection PyArgumentList
        rubber_band = QgsRubberBand(
            self._map_canvas, geometryType=QGis.Polygon)
        rubber_band.setBrushStyle(Qt.NoBrush)
        rubber_band.setColor(colour)
        rubber_band.setWidth(width)
        rubber_band.setToGeometry(geometry, None)
        return rubber_band
Example #3
0
class CoordTool(QgsMapTool):
    def __init__(self, iface):
        QgsMapTool.__init__(self, iface.mapCanvas())
        self.canvas = iface.mapCanvas()
        self.iface = iface
        self.status = 0
        self.rb = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry)
        self.rb.setColor(QColor(255, 70, 0, 200))
        self.rb.setWidth(5)
        self.rb.setBrushStyle(Qt.NoBrush)

    def canvasReleaseEvent(self, event):
        if event.button() == Qt.LeftButton:
            if self.status == 0:
                self.rb.reset(QgsWkbTypes.LineGeometry)
                self.status = 1
            self.rb.addPoint(self.toMapcoorinates(event.pos()))
        else:
            if self.rb.numberOfVertices() > 1:
                self.status = 0
                current_layer = self.iface.mapCanvas().currentLayer()
                pr = current_layer.dataProvider()
                caps = current_layer.dataProvider().capabilities()
                if caps & QgsVectorDataProvider.AddFeatures:
                    feat = QgsFeature(current_layer.fields())
                    feat.setGeometry(self.rb.asGemoetry())
                    pr.addFeature(feat)
                    current_layer.updateExtents()
                    current_layer.commitChanges()
                    current_layer.triggerRepaint()

    def canvasMoveEvent(self, event):
        if self.rb.numberOfVertices() > 0 and self.status == 1:
            self.rb.removeLastPoint(0)
            self.rb.addPoint(self.toMapCoordinates(event.pos()))
Example #4
0
 def build_rubber_band(self, points):
     rubber_band = QgsRubberBand(self.canvas(), False)  # False = not a polygon
     rubber_band.setToGeometry(QgsGeometry.fromPolyline(points), None)
     # for point in points:
     #     rubber_band.addPoint(point)
     rubber_band.setColor(QColor(255, 128, 128))
     rubber_band.setWidth(1)
     rubber_band.setBrushStyle(Qt.Dense4Pattern)
     return rubber_band
Example #5
0
class CurrentSelection(QgsRubberBand):
    """
    Position marker for the current location in the viewer.
    """

    class AniObject(QObject):
        def __init__(self, band):
            super(CurrentSelection.AniObject, self).__init__()
            self.color = QColor()

        @pyqtProperty(int)
        def alpha(self):
            return self.color.alpha()

        @alpha.setter
        def alpha(self, value):
            self.color.setAlpha(value)

    def __init__(self, canvas):
        super(CurrentSelection, self).__init__(canvas)
        self.outline = QgsRubberBand(canvas)
        self.outline.setBrushStyle(Qt.NoBrush)
        self.outline.setWidth(5)
        self.outline.setIconSize(30)
        self.aniobject = CurrentSelection.AniObject(self)
        self.anim = QPropertyAnimation(self.aniobject, "alpha")
        self.anim.setDuration(500)
        self.anim.setStartValue(50)
        self.anim.setEndValue(100)
        self.anim.valueChanged.connect(self.value_changed)

    def setOutlineColour(self, color):
        self.outline.setColor(color)

    def setToGeometry(self, geom, layer):
        super(CurrentSelection, self).setToGeometry(geom, layer)
        self.outline.setToGeometry(geom, layer)
        self.anim.stop()
        self.anim.start()

    def reset(self, geomtype=QGis.Line):
        super(CurrentSelection, self).reset(geomtype)
        self.outline.reset(geomtype)
        self.anim.stop()

    def value_changed(self, value):
        self.setColor(self.aniobject.color)
        self.update()

    def setColor(self, color):
        self.aniobject.color = color
        super(CurrentSelection, self).setColor(color)
Example #6
0
class CurrentSelection(QgsRubberBand):
    """
    Position marker for the current location in the viewer.
    """

    class AniObject(QObject):
        def __init__(self, band):
            super(CurrentSelection.AniObject, self).__init__()
            self.color = QColor()

        @pyqtProperty(int)
        def alpha(self):
            return self.color.alpha()

        @alpha.setter
        def alpha(self, value):
            self.color.setAlpha(value)

    def __init__(self, canvas):
        super(CurrentSelection, self).__init__(canvas)
        self.outline = QgsRubberBand(canvas)
        self.outline.setBrushStyle(Qt.NoBrush)
        self.outline.setWidth(5)
        self.outline.setIconSize(30)
        self.aniobject = CurrentSelection.AniObject(self)
        self.anim = QPropertyAnimation(self.aniobject, "alpha")
        self.anim.setDuration(500)
        self.anim.setStartValue(50)
        self.anim.setEndValue(100)
        self.anim.valueChanged.connect(self.value_changed)

    def setOutlineColour(self, color):
        self.outline.setColor(color)

    def setToGeometry(self, geom, layer):
        super(CurrentSelection, self).setToGeometry(geom, layer)
        self.outline.setToGeometry(geom, layer)
        self.anim.stop()
        self.anim.start()

    def reset(self, geomtype=QGis.Line):
        super(CurrentSelection, self).reset(geomtype)
        self.outline.reset(geomtype)
        self.anim.stop()

    def value_changed(self, value):
        self.setColor(self.aniobject.color)
        self.update()

    def setColor(self, color):
        self.aniobject.color = color
        super(CurrentSelection, self).setColor(color)
Example #7
0
class RectangleMapTool(QgsMapToolEmitPoint):

    boxCreated = pyqtSignal(QgsRectangle)

    def __init__(self, canvas):
        self.canvas = canvas
        QgsMapToolEmitPoint.__init__(self, self.canvas)
        self.rubberBand = QgsRubberBand(self.canvas,
                                        QgsWkbTypes().GeometryType(2))
        self.rubberBand.setColor(Qt.red)
        self.rubberBand.setBrushStyle(Qt.Dense6Pattern)
        self.rubberBand.setWidth(1)
        self.reset()

    def reset(self):
        self.startPoint = self.endPoint = None
        self.isEmittingPoint = False
        self.rubberBand.reset(QgsWkbTypes().GeometryType(2))

    def canvasPressEvent(self, e):
        self.startPoint = self.toMapCoordinates(e.pos())
        self.endPoint = self.startPoint
        self.isEmittingPoint = True
        self.showRect(self.startPoint, self.endPoint)

    def canvasReleaseEvent(self, e):
        self.isEmittingPoint = False
        r = self.rectangle()
        if r is not None:
            self.boxCreated.emit(r)

    def canvasMoveEvent(self, e):
        if not self.isEmittingPoint:
            return

        self.endPoint = self.toMapCoordinates(e.pos())
        self.showRect(self.startPoint, self.endPoint)

    def showRect(self, startPoint, endPoint):
        self.rubberBand.reset(QgsWkbTypes().GeometryType(2))
        if startPoint.x() == endPoint.x() or startPoint.y() == endPoint.y():
            return

        point1 = QgsPointXY(startPoint.x(), startPoint.y())
        point2 = QgsPointXY(startPoint.x(), endPoint.y())
        point3 = QgsPointXY(endPoint.x(), endPoint.y())
        point4 = QgsPointXY(endPoint.x(), startPoint.y())

        self.rubberBand.addPoint(point1, False)
        self.rubberBand.addPoint(point2, False)
        self.rubberBand.addPoint(point3, False)
        self.rubberBand.addPoint(point4, True)  # true to update canvas
        self.rubberBand.show()

    def rectangle(self):
        if self.startPoint is None or self.endPoint is None:
            return None
        elif self.startPoint.x() == self.endPoint.x() or self.startPoint.y(
        ) == self.endPoint.y():
            return None
        self.rubberBand.hide()

        srsCrs = self.canvas.mapSettings().destinationCrs()
        dstCrs = QgsCoordinateReferenceSystem(
            4326, QgsCoordinateReferenceSystem.EpsgCrsId)
        coordinateTransformer = QgsCoordinateTransform(srsCrs, dstCrs,
                                                       QgsProject.instance())
        rect = coordinateTransformer.transform(
            QgsRectangle(self.startPoint, self.endPoint))

        return rect
class DockEditorDialog(QtGui.QDockWidget, Ui_DockEditor):
    def __init__(self, iface, mapCanvas):
        self.iface = iface
        QtGui.QDockWidget.__init__(self)
        # Set up the user interface from Designer.
        # After setupUI you can access any designer object by doing
        # self.<objectname>, and you can use autoconnect slots - see
        # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html
        # #widgets-and-dialogs-with-auto-connect
        self.setupUi(self)
        #self.setAttribute(Qt.WA_DeleteOnClose)
        self.iface.addDockWidget(Qt.RightDockWidgetArea, self)

        self.mapCanvas = mapCanvas

        # rubber bands
        self.featureRubber = QgsRubberBand(self.mapCanvas, False)
        self.updateFeatureRubber()

        # Gui defaults
        self.kwaliteitsklasseCombo.setVisible(False)
        self.onderzochtJaarSpinBox.setVisible(False)
        self.hersteldJaarSpinBox.setVisible(False)
        self.kwaliteitsklasseLabel.setVisible(False)
        self.monitoringObjTekst.setVisible(False)

        # GUI signals connection
        self.actionButtonBox.button(QtGui.QDialogButtonBox.Save).clicked.connect(self.applySave)
        self.actionButtonBox.button(QtGui.QDialogButtonBox.Abort).clicked.connect(self.applyCancel)
        self.deleteButton.clicked.connect(self.applyDelete)
        self.newPandButton.clicked.connect(self.applyNewPand)
        self.idButton.clicked.connect(self.zoomToFeature)
        self.adresLinkButton.clicked.connect(self.showVerblijfsObj)
        self.verseonLinkButton.clicked.connect(self.getVerseonInfo)
        self.lkiLinkButton.clicked.connect(self.showLkiInfo)

        # force Dutch for QDialogButtonBox
        self.actionButtonBox.button(QtGui.QDialogButtonBox.Save).setText("Opslaan")
        self.actionButtonBox.button(QtGui.QDialogButtonBox.Abort).setText("Annuleer");

        # enable save button when value changes
        self.bezitCombo.currentIndexChanged.connect(self.actionButtonBoxEnable)
        self.kwaliteitsklasseCombo.currentIndexChanged.connect(self.actionButtonBoxEnable)
        self.onderzochtCheckBox.stateChanged.connect(self.actionButtonBoxEnable)
        self.onderzochtJaarSpinBox.valueChanged.connect(self.actionButtonBoxEnable)
        self.hersteldCheckBox.stateChanged.connect(self.actionButtonBoxEnable)
        self.hersteldJaarSpinBox.valueChanged.connect(self.actionButtonBoxEnable)
        self.projectCombo.editTextChanged.connect(self.actionButtonBoxEnable)
        self.richtlijnCombo.editTextChanged.connect(self.actionButtonBoxEnable)
        self.handhavingCheckBox.stateChanged.connect(self.actionButtonBoxEnable)
        self.typefundCombo.editTextChanged.connect(self.actionButtonBoxEnable)
        self.paallengteSpinBox.valueChanged.connect(self.actionButtonBoxEnable)
        self.houtsoortCombo.editTextChanged.connect(self.actionButtonBoxEnable)
        self.dekkingSpinBox.valueChanged.connect(self.actionButtonBoxEnable)
        self.droogstandCheckBox.stateChanged.connect(self.actionButtonBoxEnable)
        self.opmerkingText.textChanged.connect(self.actionButtonBoxEnable)

        self.setVisible(False)

    def showEvent(self, e):

        self.newGroupBox.hide()
        self.editGroupBox.hide()
        if (getVectorLayerByName(editLayerName) == None):
            self.errorFrame.show()
        else:
            self.errorFrame.hide()

        self.editLayer = getVectorLayerByName(editLayerName)
        self.adresLayer = getVectorLayerByName(adresLayerName)
        self.geomLayer = getVectorLayerByName(geomLayerName)
        self.editFeature = None
        self.updateCombos()

    def closeEvent(self, e):

        e.ignore()
        self.featureRubber.reset()
        self.hide()

    def zoomToFeature(self):
        box = self.selectedFeature.geometry().buffer(20, 4).boundingBox()
        self.iface.mapCanvas().setExtent(box)
        self.iface.mapCanvas().refresh()

    def updateCombos(self):

        if (self.editLayer):
            FieldCombo(self.bezitCombo, self.editLayer, initField="bezit")
            FieldCombo(self.kwaliteitsklasseCombo, self.editLayer, initField="kwaliteitsklasse")
            FieldCombo(self.projectCombo, self.editLayer, initField="project")
            FieldCombo(self.richtlijnCombo, self.editLayer, initField="richtlijn")
            FieldCombo(self.typefundCombo, self.editLayer, initField="funderingtype")
            FieldCombo(self.houtsoortCombo, self.editLayer, initField="houtsoort")

    def actionButtonBoxEnable(self):
        self.actionButtonBox.setDisabled(False)

    def featureSelected(self, layer, feature):
        self.layer = layer
        self.selectedFeature = feature
        initialGeometry = QgsGeometry(feature.geometry())

        self.featureRubber.reset()
        self.featureRubber.setToGeometry(initialGeometry, layer)
        self.updateGui(feature)

    def updateGui(self, feature):
        # The important part: get the feature iterator with an expression
        request = QgsFeatureRequest().setFilterExpression ( u'"pand_id" = \'' + unicode(feature['gebouwnummer']) + '\'' )
        request.setFlags( QgsFeatureRequest.NoGeometry )
        features = self.geomLayer.getFeatures( request )

        atr = None
        fields = self.geomLayer.pendingFields()
        field_names = [field.name() for field in fields]
        for elem in features:
            self.editFeature = elem
            atr = dict(zip(field_names, elem.attributes()))

        if atr is not None:
            atr = {i:j for i,j in atr.items() if j }
            self.newGroupBox.hide()
            self.editGroupBox.show()
            self.verwijderWidget.show()
            self.showValues(feature, atr)
            self.actionButtonBox.setDisabled(True)

        else:
            self.newGroupBox.show()
            self.editGroupBox.hide()
            self.verwijderWidget.hide()

            self.editFeature = None
            self.statusNieuwLabel.setText(unicode(feature['status']))
            self.pandidLabel.setText(unicode(feature['gebouwnummer']))

    def showValues(self, feature, atr):

        self.deleteCheckBox.setChecked(False)

        self.idText.setText(unicode(atr.get("pand_id", "")))
        if (feature['vboj_count'] == 1):
            self.adresLinkButton.setText(BagadresString().request(unicode(feature['gebouwnummer'])))
        else:
            self.adresLinkButton.setText("Pand bevat " + unicode(feature['vboj_count']) + " adres(sen)")

        self.setComboTekst(self.bezitCombo, atr.get("bezit", ""))

        if (atr.get("onderzocht", "")=="t"):
            self.onderzochtCheckBox.setChecked(True)
            self.onderzochtJaarSpinBox.setValue(atr.get("onderzocht_jaar", ""))
        else:
            self.onderzochtCheckBox.setChecked(False)

        self.setComboTekst(self.kwaliteitsklasseCombo, atr.get("kwaliteitsklasse", ""))

        if (atr.get("hersteld", "")=="t"):
            self.hersteldCheckBox.setChecked(True)
            self.hersteldJaarSpinBox.setValue(atr.get("hersteld_jaar", ""))
        else:
            self.hersteldCheckBox.setChecked(False)

        self.setComboTekst(self.projectCombo, atr.get("project", ""))
        self.setComboTekst(self.richtlijnCombo, atr.get("richtlijn", ""))
        
        self.handhavingCheckBox.setChecked(toBoolean(atr.get("fumon_monitoring", "f")))
        self.monitoringObjTekst.setText(atr.get("fumon_objectcode", ""))

        self.setComboTekst(self.typefundCombo, atr.get("funderingtype", ""))
        self.paallengteSpinBox.setValue(atr.get("paallengte", 0))
        self.setComboTekst(self.houtsoortCombo, atr.get("houtsoort", ""))
        self.dekkingSpinBox.setValue(atr.get("dekking", 0))
        self.droogstandCheckBox.setChecked(toBoolean(atr.get("droogstand", "f")))

        self.opmerkingText.setPlainText(atr.get("opmerking", ""))      

        # set texts in UI
        featureStatus = unicode(feature['status'])
        featureBj = unicode(feature['bouwjaar'])

        self.statusText.setText(featureStatus)
        self.bouwjaarText.setText(featureBj)

    def showVerblijfsObj(self):
        feature = self.selectedFeature
        gebouwnummer = unicode(feature['gebouwnummer'])
        InfoBagDialog(gebouwnummer, self.iface)

    def getVerseonInfo(self):
        feature = self.selectedFeature
        gebouwnummer = unicode(feature['gebouwnummer'])
        InfoVerseonDialog(gebouwnummer, self.iface)

    def showLkiInfo(self):
        feature = self.selectedFeature
        wkt = feature.geometry().pointOnSurface().exportToWkt()
        PerceelinfoDialog(wkt, self.iface)

    def setComboTekst(self, combo, value):
        idx = combo.findText(value)
        combo.setCurrentIndex(idx)

    def updateFeatureRubber(self):
        self.featureRubber.setColor(featureRubberColor)
        self.featureRubber.setWidth(featureRubberSize)
        self.featureRubber.setBrushStyle(Qt.NoBrush)
        self.mapCanvas.refresh()

    def applySave(self):

        if not self.editLayer.isEditable():
            self.editLayer.startEditing()

        layer = self.editLayer
        layer.beginEditCommand("Funderingsgegevens")

        try:

            if (self.editFeature == None):
                idx = layer.pendingFields().indexFromName("id")
                nextId = layer.maximumValue(idx)
                feat = QgsFeature()
                feat.setFields(layer.pendingFields(), True)
                feat.setAttribute(layer.fieldNameIndex( "pand_id" ), self.pandidLabel.text())
                feat.setAttribute(layer.fieldNameIndex( "id" ), nextId + 1)
                layer.addFeature(feat, False)
                self.editFeature = feat

            fid = self.editFeature.id()

            layer.changeAttributeValue(fid, layer.fieldNameIndex( "pand_id" ), self.idText.text())
            layer.changeAttributeValue(fid, layer.fieldNameIndex( "bezit" ), self.bezitCombo.currentText())
            layer.changeAttributeValue(fid, layer.fieldNameIndex( "onderzocht" ), self.onderzochtCheckBox.isChecked())
            if (self.onderzochtCheckBox.isChecked()):
                layer.changeAttributeValue(fid, layer.fieldNameIndex( "onderzocht_jaar" ), self.onderzochtJaarSpinBox.value())
            else:
                layer.changeAttributeValue(fid, layer.fieldNameIndex( "onderzocht_jaar" ), None)
                
            layer.changeAttributeValue(fid, layer.fieldNameIndex( "kwaliteitsklasse" ), self.kwaliteitsklasseCombo.currentText())

            layer.changeAttributeValue(fid, layer.fieldNameIndex( "hersteld" ), self.hersteldCheckBox.isChecked())
            if (self.hersteldCheckBox.isChecked()):
                layer.changeAttributeValue(fid, layer.fieldNameIndex( "hersteld_jaar" ), self.hersteldJaarSpinBox.value())
            else:
                layer.changeAttributeValue(fid, layer.fieldNameIndex( "hersteld_jaar" ), None)

            layer.changeAttributeValue(fid, layer.fieldNameIndex( "project" ), self.projectCombo.currentText())
            layer.changeAttributeValue(fid, layer.fieldNameIndex( "richtlijn" ), self.richtlijnCombo.currentText())

            layer.changeAttributeValue(fid, layer.fieldNameIndex( "funderingtype" ), self.typefundCombo.currentText())
            layer.changeAttributeValue(fid, layer.fieldNameIndex( "paallengte" ), self.paallengteSpinBox.value())
            layer.changeAttributeValue(fid, layer.fieldNameIndex( "houtsoort" ), self.houtsoortCombo.currentText())
            layer.changeAttributeValue(fid, layer.fieldNameIndex( "dekking" ), self.dekkingSpinBox.value())
            layer.changeAttributeValue(fid, layer.fieldNameIndex( "droogstand" ), self.droogstandCheckBox.isChecked())

            layer.changeAttributeValue(fid, layer.fieldNameIndex( "opmerking" ), self.opmerkingText.toPlainText())
        except Exception, e:
            raise e
            layer.destroyEditCommand()
            self.iface.messageBar().pushMessage("Funderingsherstel", "Er is wat misgegaan tijdens het opslaan van dit BAG object!", level=QgsMessageBar.CRITICAL)
        else:
class SwissLocatorFilter(QgsLocatorFilter):

    HEADERS = {
        b'User-Agent': b'Mozilla/5.0 QGIS Swiss Geoportal Locator Filter'
    }

    message_emitted = pyqtSignal(str, str, Qgis.MessageLevel, QWidget)

    def __init__(self,
                 filter_type: FilterType,
                 iface: QgisInterface = None,
                 crs: str = None):
        """"
        :param filter_type: the type of filter
        :param locale_lang: the language of the locale.
        :param iface: QGIS interface, given when on the main thread (which will display/trigger results), None otherwise
        :param crs: if iface is not given, it shall be provided, see clone()
        """
        super().__init__()
        self.type = filter_type
        self.rubber_band = None
        self.feature_rubber_band = None
        self.iface = iface
        self.map_canvas = None
        self.settings = Settings()
        self.transform_ch = None
        self.transform_4326 = None
        self.map_tip = None
        self.current_timer = None
        self.crs = None
        self.event_loop = None
        self.result_found = False
        self.access_managers = {}
        self.nam_map_tip = None
        self.nam_fetch_feature = None

        if crs:
            self.crs = crs

        self.lang = get_language()

        self.searchable_layers = searchable_layers(self.lang, restrict=True)

        if iface is not None:
            # happens only in main thread
            self.map_canvas = iface.mapCanvas()
            self.map_canvas.destinationCrsChanged.connect(
                self.create_transforms)

            self.rubber_band = QgsRubberBand(self.map_canvas,
                                             QgsWkbTypes.PointGeometry)
            self.rubber_band.setColor(QColor(255, 255, 50, 200))
            self.rubber_band.setIcon(self.rubber_band.ICON_CIRCLE)
            self.rubber_band.setIconSize(15)
            self.rubber_band.setWidth(4)
            self.rubber_band.setBrushStyle(Qt.NoBrush)

            self.feature_rubber_band = QgsRubberBand(
                self.map_canvas, QgsWkbTypes.PolygonGeometry)
            self.feature_rubber_band.setColor(QColor(255, 50, 50, 200))
            self.feature_rubber_band.setFillColor(QColor(255, 255, 50, 160))
            self.feature_rubber_band.setBrushStyle(Qt.SolidPattern)
            self.feature_rubber_band.setLineStyle(Qt.SolidLine)
            self.feature_rubber_band.setWidth(4)

            self.create_transforms()

    def name(self):
        return '{}_{}'.format(self.__class__.__name__,
                              FilterType(self.type).name)

    def clone(self):
        return SwissLocatorFilter(self.type, crs=self.crs)

    def priority(self):
        return self.settings.value(
            '{type}_priority'.format(type=self.type.value))

    def displayName(self):
        if self.type is FilterType.Location:
            return self.tr('Swiss Geoportal locations')
        elif self.type is FilterType.WMS:
            return self.tr('Swiss Geoportal / opendata.swiss WMS layers')
        elif self.type is FilterType.Feature:
            return self.tr('Swiss Geoportal features')
        else:
            raise NameError('Filter type is not valid.')

    def prefix(self):
        if self.type is FilterType.Location:
            return 'chl'
        elif self.type is FilterType.WMS:
            return 'chw'
        elif self.type is FilterType.Feature:
            return 'chf'
        else:
            raise NameError('Filter type is not valid.')

    def clearPreviousResults(self):
        self.rubber_band.reset(QgsWkbTypes.PointGeometry)
        self.feature_rubber_band.reset(QgsWkbTypes.PolygonGeometry)
        if self.map_tip is not None:
            del self.map_tip
            self.map_tip = None
        if self.current_timer is not None:
            self.current_timer.stop()
            self.current_timer.deleteLater()
            self.current_timer = None

    def hasConfigWidget(self):
        return True

    def openConfigWidget(self, parent=None):
        dlg = ConfigDialog(parent)
        wid = dlg.findChild(QTabWidget, "tabWidget", Qt.FindDirectChildrenOnly)
        tab = wid.findChild(QWidget, self.type.value)
        wid.setCurrentWidget(tab)
        dlg.exec_()

    def create_transforms(self):
        # this should happen in the main thread
        self.crs = self.settings.value('crs')
        if self.crs == 'project':
            map_crs = self.map_canvas.mapSettings().destinationCrs()
            if map_crs.isValid():
                self.crs = map_crs.authid().split(':')[1]
            if self.crs not in AVAILABLE_CRS:
                self.crs = '2056'
        assert self.crs in AVAILABLE_CRS
        src_crs_ch = QgsCoordinateReferenceSystem('EPSG:{}'.format(self.crs))
        assert src_crs_ch.isValid()
        dst_crs = self.map_canvas.mapSettings().destinationCrs()
        self.transform_ch = QgsCoordinateTransform(src_crs_ch, dst_crs,
                                                   QgsProject.instance())

        src_crs_4326 = QgsCoordinateReferenceSystem('EPSG:4326')
        self.transform_4326 = QgsCoordinateTransform(src_crs_4326, dst_crs,
                                                     QgsProject.instance())

    def group_info(self, group: str) -> (str, str):
        groups = {
            'zipcode': {
                'name': self.tr('ZIP code'),
                'layer': 'ch.swisstopo-vd.ortschaftenverzeichnis_plz'
            },
            'gg25': {
                'name': self.tr('Municipal boundaries'),
                'layer': 'ch.swisstopo.swissboundaries3d-gemeinde-flaeche.fill'
            },
            'district': {
                'name': self.tr('District'),
                'layer': 'ch.swisstopo.swissboundaries3d-bezirk-flaeche.fill'
            },
            'kantone': {
                'name': self.tr('Cantons'),
                'layer': 'ch.swisstopo.swissboundaries3d-kanton-flaeche.fill'
            },
            'gazetteer': {
                'name': self.tr('Index'),
                'layer': 'ch.swisstopo.swissnames3d'
            },  # there is also: ch.bav.haltestellen-oev ?
            'address': {
                'name': self.tr('Address'),
                'layer': 'ch.bfs.gebaeude_wohnungs_register'
            },
            'parcel': {
                'name': self.tr('Parcel'),
                'layer': None
            }
        }
        if group not in groups:
            self.info('Could not find group {} in dictionary'.format(group))
            return None, None
        return groups[group]['name'], groups[group]['layer']

    @staticmethod
    def rank2priority(rank) -> float:
        """
        Translate the rank from geoportal to the priority of the result
        see https://api3.geo.admin.ch/services/sdiservices.html#search
        :param rank: an integer from 1 to 7
        :return: the priority as a float from 0 to 1, 1 being a perfect match
        """
        return float(-rank / 7 + 1)

    @staticmethod
    def box2geometry(box: str) -> QgsRectangle:
        """
        Creates a rectangle from a Box definition as string
        :param box: the box as a string
        :return: the rectangle
        """
        coords = re.findall(r'\b(\d+(?:\.\d+)?)\b', box)
        if len(coords) != 4:
            raise InvalidBox('Could not parse: {}'.format(box))
        return QgsRectangle(float(coords[0]), float(coords[1]),
                            float(coords[2]), float(coords[3]))

    @staticmethod
    def url_with_param(url, params) -> str:
        url = QUrl(url)
        q = QUrlQuery(url)
        for key, value in params.items():
            q.addQueryItem(key, value)
        url.setQuery(q)
        return url.url()

    def fetchResults(self, search: str, context: QgsLocatorContext,
                     feedback: QgsFeedback):
        try:
            self.dbg_info("start Swiss locator search...")

            if len(search) < 2:
                return

            if len(search) < 4 and self.type is FilterType.Feature:
                return

            self.result_found = False

            swisstopo_base_url = 'https://api3.geo.admin.ch/rest/services/api/SearchServer'
            swisstopo_base_params = {
                'type':
                self.type.value,
                'searchText':
                str(search),
                'returnGeometry':
                'true',
                'lang':
                self.lang,
                'sr':
                self.crs,
                'limit':
                str(
                    self.settings.value(
                        '{type}_limit'.format(type=self.type.value)))
                # bbox Must be provided if the searchText is not.
                # A comma separated list of 4 coordinates representing
                # the bounding box on which features should be filtered (SRID: 21781).
            }
            # Locations, WMS layers
            if self.type is not FilterType.Feature:
                nam = NetworkAccessManager()
                feedback.canceled.connect(nam.abort)

                search_urls = [(swisstopo_base_url, swisstopo_base_params)]

                if self.settings.value('layers_include_opendataswiss'
                                       ) and self.type is FilterType.WMS:
                    search_urls.append(
                        ('https://opendata.swiss/api/3/action/package_search?',
                         {
                             'q': 'q=WMS+%C3' + str(search)
                         }))

                for (swisstopo_base_url, swisstopo_base_params) in search_urls:
                    swisstopo_base_url = self.url_with_param(
                        swisstopo_base_url, swisstopo_base_params)
                    self.dbg_info(swisstopo_base_url)
                    try:
                        (response, content) = nam.request(swisstopo_base_url,
                                                          headers=self.HEADERS,
                                                          blocking=True)
                        self.handle_response(response, search, feedback)
                    except RequestsExceptionUserAbort:
                        pass
                    except RequestsException as err:
                        self.info(err)

            # Feature search
            else:
                # Feature search is split in several requests
                # otherwise URL is too long
                self.access_managers = {}
                try:
                    layers = list(self.searchable_layers.keys())
                    assert len(layers) > 0
                    step = 30
                    for l in range(0, len(layers), step):
                        last = min(l + step - 1, len(layers) - 1)
                        swisstopo_base_params['features'] = ','.join(
                            layers[l:last])
                        self.access_managers[self.url_with_param(
                            swisstopo_base_url, swisstopo_base_params)] = None
                except IOError:
                    self.info(
                        'Layers data file not found. Please report an issue.',
                        Qgis.Critical)

                # init event loop
                # wait for all requests to end
                self.event_loop = QEventLoop()

                def reply_finished(response):
                    self.handle_response(response, search, feedback)
                    if response.url in self.access_managers:
                        self.access_managers[response.url] = None
                    for nam in self.access_managers.values():
                        if nam is not None:
                            return
                        self.event_loop.quit()

                feedback.canceled.connect(self.event_loop.quit)

                # init the network access managers, create the URL
                for swisstopo_base_url in self.access_managers:
                    self.dbg_info(swisstopo_base_url)
                    nam = NetworkAccessManager()
                    self.access_managers[swisstopo_base_url] = nam
                    nam.finished.connect(reply_finished)
                    nam.request(swisstopo_base_url,
                                headers=self.HEADERS,
                                blocking=False)
                    feedback.canceled.connect(nam.abort)

                # Let the requests end and catch all exceptions (and clean up requests)
                if len(self.access_managers) > 0:
                    try:
                        self.event_loop.exec_(
                            QEventLoop.ExcludeUserInputEvents)
                    except RequestsExceptionUserAbort:
                        pass
                    except RequestsException as err:
                        self.info(str(err))

            if not self.result_found:
                result = QgsLocatorResult()
                result.filter = self
                result.displayString = self.tr('No result found.')
                result.userData = NoResult().as_definition()
                self.resultFetched.emit(result)

        except Exception as e:
            self.info(e, Qgis.Critical)
            exc_type, exc_obj, exc_traceback = sys.exc_info()
            filename = os.path.split(
                exc_traceback.tb_frame.f_code.co_filename)[1]
            self.info(
                '{} {} {}'.format(exc_type, filename, exc_traceback.tb_lineno),
                Qgis.Critical)
            self.info(
                traceback.print_exception(exc_type, exc_obj, exc_traceback),
                Qgis.Critical)

    def handle_response(self, response, search: str, feedback: QgsFeedback):
        try:
            if response.status_code != 200:
                if not isinstance(response.exception,
                                  RequestsExceptionUserAbort):
                    self.info(
                        "Error in main response with status code: {} from {}".
                        format(response.status_code, response.url))
                return

            data = json.loads(response.content.decode('utf-8'))
            # self.dbg_info(data)

            if self.is_opendata_swiss_response(data):
                visited_capabilities = []

                for loc in data['result']['results']:
                    display_name = loc['title'].get(self.lang, "")
                    if not display_name:
                        # Fallback to german
                        display_name = loc['title']['de']

                    for res in loc['resources']:

                        url = res['url']
                        url_components = urlparse(url)
                        wms_url = url_components.scheme + '://' + url_components.netloc + '/' + url_components.path + '?'

                        result = QgsLocatorResult()
                        result.filter = self
                        result.group = 'opendata.swiss'
                        result.icon = QgsApplication.getThemeIcon(
                            "/mActionAddWmsLayer.svg")

                        if 'wms' in url.lower():
                            if res['media_type'] == 'WMS':
                                result.displayString = display_name
                                result.description = url

                                if res['title']['de'] == 'GetMap':
                                    layers = parse_qs(
                                        url_components.query)['LAYERS']
                                    result.userData = WMSLayerResult(
                                        layer=layers[0],
                                        title=display_name,
                                        url=wms_url).as_definition()
                                    self.result_found = True
                                    self.resultFetched.emit(result)

                            elif 'request=getcapabilities' in url.lower(
                            ) and url_components.netloc not in visited_capabilities:
                                visited_capabilities.append(
                                    url_components.netloc)

                                def parse_capabilities_result(response):
                                    capabilities = ET.fromstring(
                                        response.content)

                                    # Get xml namespace
                                    match = re.match(r'\{.*\}',
                                                     capabilities.tag)
                                    namespace = match.group(0) if match else ''

                                    # Search for layers containing the search term in the name or title
                                    for layer in capabilities.findall(
                                            './/{}Layer'.format(namespace)):
                                        layername = self.find_text(
                                            layer, '{}Name'.format(namespace))
                                        layertitle = self.find_text(
                                            layer, '{}Title'.format(namespace))
                                        if layername and (
                                                search in layername.lower() or
                                                search in layertitle.lower()):
                                            if not layertitle:
                                                layertitle = layername

                                            result.displayString = layertitle
                                            result.description = '{}?LAYERS={}'.format(
                                                url.replace(
                                                    'GetCapabilities',
                                                    'GetMap'), layername)
                                            result.userData = WMSLayerResult(
                                                layer=layername,
                                                title=layertitle,
                                                url=wms_url).as_definition()
                                            self.result_found = True
                                            self.resultFetched.emit(result)

                                    self.event_loop.quit()

                                # Retrieve Capabilities xml
                                self.event_loop = QEventLoop()
                                nam = NetworkAccessManager()
                                nam.finished.connect(parse_capabilities_result)
                                nam.request(url,
                                            headers=self.HEADERS,
                                            blocking=False)
                                feedback.canceled.connect(self.event_loop.quit)

                                try:
                                    self.event_loop.exec_(
                                        QEventLoop.ExcludeUserInputEvents)
                                except RequestsExceptionUserAbort:
                                    pass
                                except RequestsException as err:
                                    self.info(err)

            else:
                for loc in data['results']:
                    self.dbg_info("keys: {}".format(loc['attrs'].keys()))

                    result = QgsLocatorResult()
                    result.filter = self
                    result.group = 'Swiss Geoportal'
                    if loc['attrs']['origin'] == 'layer':
                        # available keys: ['origin', 'lang', 'layer', 'staging', 'title', 'topics', 'detail', 'label', 'id']
                        for key, val in loc['attrs'].items():
                            self.dbg_info('{}: {}'.format(key, val))
                        result.displayString = loc['attrs']['title']
                        result.description = loc['attrs']['layer']
                        result.userData = WMSLayerResult(
                            layer=loc['attrs']['layer'],
                            title=loc['attrs']['title'],
                            url='http://wms.geo.admin.ch/?VERSION%3D2.0.0'
                        ).as_definition()
                        result.icon = QgsApplication.getThemeIcon(
                            "/mActionAddWmsLayer.svg")
                        self.result_found = True
                        self.resultFetched.emit(result)

                    elif loc['attrs']['origin'] == 'feature':
                        for key, val in loc['attrs'].items():
                            self.dbg_info('{}: {}'.format(key, val))
                        layer = loc['attrs']['layer']
                        point = QgsPointXY(loc['attrs']['lon'],
                                           loc['attrs']['lat'])
                        if layer in self.searchable_layers:
                            layer_display = self.searchable_layers[layer]
                        else:
                            self.info(
                                self.
                                tr('Layer {} is not in the list of searchable layers.'
                                   ' Please report issue.'.format(layer)),
                                Qgis.Warning)
                            layer_display = layer
                        result.group = layer_display
                        result.displayString = loc['attrs']['detail']
                        result.userData = FeatureResult(
                            point=point,
                            layer=layer,
                            feature_id=loc['attrs']
                            ['feature_id']).as_definition()
                        result.icon = QIcon(
                            ":/plugins/swiss_locator/icons/swiss_locator.png")
                        self.result_found = True
                        self.resultFetched.emit(result)

                    else:  # locations
                        for key, val in loc['attrs'].items():
                            self.dbg_info('{}: {}'.format(key, val))
                        group_name, group_layer = self.group_info(
                            loc['attrs']['origin'])
                        if 'layerBodId' in loc['attrs']:
                            self.dbg_info("layer: {}".format(
                                loc['attrs']['layerBodId']))
                        if 'featureId' in loc['attrs']:
                            self.dbg_info("feature: {}".format(
                                loc['attrs']['featureId']))

                        result.displayString = strip_tags(
                            loc['attrs']['label'])
                        # result.description = loc['attrs']['detail']
                        # if 'featureId' in loc['attrs']:
                        #     result.description = loc['attrs']['featureId']
                        result.group = group_name
                        result.userData = LocationResult(
                            point=QgsPointXY(loc['attrs']['y'],
                                             loc['attrs']['x']),
                            bbox=self.box2geometry(
                                loc['attrs']['geom_st_box2d']),
                            layer=group_layer,
                            feature_id=loc['attrs']['featureId']
                            if 'featureId' in loc['attrs'] else None,
                            html_label=loc['attrs']['label']).as_definition()
                        result.icon = QIcon(
                            ":/plugins/swiss_locator/icons/swiss_locator.png")
                        self.result_found = True
                        self.resultFetched.emit(result)

        except Exception as e:
            self.info(str(e), Qgis.Critical)
            exc_type, exc_obj, exc_traceback = sys.exc_info()
            filename = os.path.split(
                exc_traceback.tb_frame.f_code.co_filename)[1]
            self.info(
                '{} {} {}'.format(exc_type, filename, exc_traceback.tb_lineno),
                Qgis.Critical)
            self.info(
                traceback.print_exception(exc_type, exc_obj, exc_traceback),
                Qgis.Critical)

    def triggerResult(self, result: QgsLocatorResult):
        # this should be run in the main thread, i.e. mapCanvas should not be None

        # remove any map tip
        self.clearPreviousResults()

        user_data = NoResult
        try:
            swiss_result = result_from_data(result)
        except SystemError:
            self.message_emitted.emit(
                self.displayName(),
                self.
                tr('QGIS Swiss Locator encountered an error. Please <b>update to QGIS 3.16.2</b> or newer.'
                   ), Qgis.Warning, None)

        if type(swiss_result) == NoResult:
            return

        # WMS
        if type(swiss_result) == WMSLayerResult:
            url_with_params = 'contextualWMSLegend=0' \
                              '&crs=EPSG:{crs}' \
                              '&dpiMode=7' \
                              '&featureCount=10' \
                              '&format=image/png' \
                              '&layers={layer}' \
                              '&styles=' \
                              '&url={url}' \
                .format(crs=self.crs, layer=swiss_result.layer, url=swiss_result.url)
            wms_layer = QgsRasterLayer(url_with_params, result.displayString,
                                       'wms')
            label = QLabel()
            label.setTextFormat(Qt.RichText)
            label.setTextInteractionFlags(Qt.TextBrowserInteraction)
            label.setOpenExternalLinks(True)

            if 'geo.admin.ch' in swiss_result.url.lower():
                label.setText(
                    '<a href="https://map.geo.admin.ch/'
                    '?lang={}&bgLayer=ch.swisstopo.pixelkarte-farbe&layers={}">'
                    'Open layer in map.geo.admin.ch</a>'.format(
                        self.lang, swiss_result.layer))

            if not wms_layer.isValid():
                msg = self.tr('Cannot load WMS layer: {} ({})'.format(
                    swiss_result.title, swiss_result.layer))
                level = Qgis.Warning
                self.info(msg, level)
            else:
                msg = self.tr('WMS layer added to the map: {} ({})'.format(
                    swiss_result.title, swiss_result.layer))
                level = Qgis.Info

                QgsProject.instance().addMapLayer(wms_layer)

            self.message_emitted.emit(self.displayName(), msg, level, label)

        # Feature
        elif type(swiss_result) == FeatureResult:
            point = QgsGeometry.fromPointXY(swiss_result.point)
            point.transform(self.transform_4326)
            self.highlight(point)
            if self.settings.value('show_map_tip'):
                self.show_map_tip(swiss_result.layer, swiss_result.feature_id,
                                  point)
        # Location
        else:
            point = QgsGeometry.fromPointXY(swiss_result.point)
            if swiss_result.bbox.isNull():
                bbox = None
            else:
                bbox = QgsGeometry.fromRect(swiss_result.bbox)
                bbox.transform(self.transform_ch)
            layer = swiss_result.layer
            feature_id = swiss_result.feature_id
            if not point:
                return

            point.transform(self.transform_ch)

            self.highlight(point, bbox)

            if layer and feature_id:
                self.fetch_feature(layer, feature_id)

                if self.settings.value('show_map_tip'):
                    self.show_map_tip(layer, feature_id, point)
            else:
                self.current_timer = QTimer()
                self.current_timer.timeout.connect(self.clearPreviousResults)
                self.current_timer.setSingleShot(True)
                self.current_timer.start(5000)

    def highlight(self, point, bbox=None):
        if bbox is None:
            bbox = point
        self.rubber_band.reset(QgsWkbTypes.PointGeometry)
        self.rubber_band.addGeometry(point, None)
        rect = bbox.boundingBox()
        rect.scale(1.1)
        self.map_canvas.setExtent(rect)
        self.map_canvas.refresh()

    def fetch_feature(self, layer, feature_id):
        # Try to get more info
        self.nam_fetch_feature = NetworkAccessManager()
        url_detail = 'https://api3.geo.admin.ch/rest/services/api/MapServer/{layer}/{feature_id}' \
            .format(layer=layer, feature_id=feature_id)
        params = {'lang': self.lang, 'sr': self.crs}
        url_detail = self.url_with_param(url_detail, params)
        self.dbg_info(url_detail)
        self.nam_fetch_feature.finished.connect(self.parse_feature_response)
        self.nam_fetch_feature.request(url_detail,
                                       headers=self.HEADERS,
                                       blocking=False)

    def parse_feature_response(self, response):
        if response.status_code != 200:
            if not isinstance(response.exception, RequestsExceptionUserAbort):
                self.info(
                    "Error in feature response with status code: {} from {}".
                    format(response.status_code, response.url))
            return

        data = json.loads(response.content.decode('utf-8'))
        self.dbg_info(data)

        if 'feature' not in data or 'geometry' not in data['feature']:
            return

        if 'rings' in data['feature']['geometry']:
            rings = data['feature']['geometry']['rings']
            self.dbg_info(rings)
            for r in range(0, len(rings)):
                for p in range(0, len(rings[r])):
                    rings[r][p] = QgsPointXY(rings[r][p][0], rings[r][p][1])
            geometry = QgsGeometry.fromPolygonXY(rings)
            geometry.transform(self.transform_ch)

            self.feature_rubber_band.reset(QgsWkbTypes.PolygonGeometry)
            self.feature_rubber_band.addGeometry(geometry, None)

    def show_map_tip(self, layer, feature_id, point):
        if layer and feature_id:
            url_html = 'https://api3.geo.admin.ch/rest/services/api/MapServer/{layer}/{feature_id}/htmlPopup' \
                .format(layer=layer, feature_id=feature_id)
            params = {'lang': self.lang, 'sr': self.crs}
            url_html = self.url_with_param(url_html, params)
            self.dbg_info(url_html)

            self.nam_map_tip = NetworkAccessManager()
            self.nam_map_tip.finished.connect(
                lambda response: self.parse_map_tip_response(response, point))
            self.nam_map_tip.request(url_html,
                                     headers=self.HEADERS,
                                     blocking=False)

    def parse_map_tip_response(self, response, point):
        if response.status_code != 200:
            if not isinstance(response.exception, RequestsExceptionUserAbort):
                self.info(
                    "Error in map tip response with status code: {} from {}".
                    format(response.status_code, response.url))
            return

        self.dbg_info(response.content.decode('utf-8'))
        self.map_tip = MapTip(self.iface, response.content.decode('utf-8'),
                              point.asPoint())
        self.map_tip.closed.connect(self.clearPreviousResults)

    def info(self, msg="", level=Qgis.Info):
        self.logMessage(str(msg), level)

    def dbg_info(self, msg=""):
        if DEBUG:
            self.info(msg)

    @staticmethod
    def break_camelcase(identifier):
        matches = re.finditer(
            '.+?(?:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|$)',
            identifier)
        return ' '.join([m.group(0) for m in matches])

    def is_opendata_swiss_response(self, json):
        return 'opendata.swiss' in json.get("help", [])

    def find_text(self, xmlElement, match):
        node = xmlElement.find(match)
        return node.text if node is not None else ''
Example #10
0
class ReverseGeocodeTool(QgsMapTool):
    def __init__(self, iface, settings):
        self.canvas = iface.mapCanvas()
        QgsMapTool.__init__(self, self.canvas)
        self.iface = iface
        self.settings = settings
        self.reverseGeoCodeDialog = ReverseGeocodeDialog(
            self, self.iface, self.iface.mainWindow())
        self.iface.addDockWidget(Qt.TopDockWidgetArea,
                                 self.reverseGeoCodeDialog)
        self.reverseGeoCodeDialog.hide()
        self.epsg4326 = QgsCoordinateReferenceSystem('EPSG:4326')
        self.reply = None
        self.marker = None

        # Set up a polygon/line rubber band
        self.rubber = QgsRubberBand(self.canvas)
        self.rubber.setColor(QColor(255, 70, 0, 200))
        self.rubber.setWidth(5)
        self.rubber.setBrushStyle(Qt.NoBrush)

    def activate(self):
        '''When activated set the cursor to a crosshair.'''
        self.canvas.setCursor(Qt.CrossCursor)
        self.show()

    def unload(self):
        self.iface.removeDockWidget(self.reverseGeoCodeDialog)
        self.reverseGeoCodeDialog = None
        if self.rubber:
            self.canvas.scene().removeItem(self.rubber)
            del self.rubber
        self.removeMarker()

    def addMarker(self, lat, lon):
        if self.marker:
            self.removeMarker()
        canvasCrs = self.canvas.mapSettings().destinationCrs()
        transform = QgsCoordinateTransform(self.epsg4326, canvasCrs,
                                           QgsProject.instance())
        center = transform.transform(lon, lat)
        self.marker = QgsVertexMarker(self.canvas)
        self.marker.setCenter(center)
        self.marker.setColor(QColor(255, 70, 0))
        self.marker.setIconSize(15)
        self.marker.setIconType(QgsVertexMarker.ICON_X)
        self.marker.setPenWidth(3)
        self.marker.show()

    def removeMarker(self):
        if self.marker:
            self.canvas.scene().removeItem(self.marker)
            self.marker = None

    def clearSelection(self):
        self.removeMarker()
        self.rubber.reset()

    def transform_geom(self, geometry):
        canvasCrs = self.canvas.mapSettings().destinationCrs()
        geom = QgsGeometry(geometry)
        geom.transform(
            QgsCoordinateTransform(self.epsg4326, canvasCrs,
                                   QgsProject.instance()))
        return geom

    def show(self):
        self.reverseGeoCodeDialog.show()

    def canvasReleaseEvent(self, event):
        # Make sure the point is transfored to 4326
        pt = self.toMapCoordinates(event.pos())
        canvasCRS = self.canvas.mapSettings().destinationCrs()
        transform = QgsCoordinateTransform(canvasCRS, self.epsg4326,
                                           QgsProject.instance())
        pt = transform.transform(pt.x(), pt.y())
        url = '{}?format=json&lat={:f}&lon={:f}&zoom={:d}&addressdetails=0&polygon_text=1'.format(
            self.settings.reverseURL(), pt.y(), pt.x(),
            self.settings.levelOfDetail)
        # print url
        qurl = QUrl(url)
        if self.reply is not None:
            self.reply.finished.disconnect(self.replyFinished)
            self.reply.abort()
            self.reply = None
        request = QNetworkRequest(qurl)
        request.setRawHeader(
            b"User-Agent",
            b"Mozilla/5.0 (Windows NT 6.1: WOW64; rv:52.0) Gecko/20100101 Firefox/52.0"
        )
        request.setRawHeader(b"Connection", b"keep-alive")
        request.setRawHeader(
            b"Accept",
            b"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
        self.reply = QgsNetworkAccessManager.instance().get(request)
        self.reply.finished.connect(self.replyFinished)
        if not self.reverseGeoCodeDialog.isVisible():
            self.show()

    def setText(self, text):
        self.reverseGeoCodeDialog.addressLineEdit.setText(text)

    @pyqtSlot()
    def replyFinished(self):
        error = self.reply.error()
        self.clearSelection()
        if error == QNetworkReply.NoError:
            data = self.reply.readAll().data()
            try:
                jd = json.loads(data)
                try:
                    display_name = jd['display_name']
                    self.setText(display_name)
                except KeyError:
                    self.setText("[Could not find address]")
                try:
                    wkt = jd['geotext']
                    geometry = QgsGeometry.fromWkt(wkt)
                    geometry = self.transform_geom(geometry)
                    self.rubber.addGeometry(geometry, None)
                    self.rubber.show()
                except KeyError:
                    try:
                        lon = float(jd['lon'])
                        lat = float(jd['lat'])
                        self.addMarker(lat, lon)
                    except:
                        pass
            except:
                self.setText("Error: " + data)

        else:
            self.setText("[Address error]")
        self.reply.deleteLater()
        self.reply = None
Example #11
0
class FinderBox(QComboBox):

    running = False
    toFinish = 0

    searchStarted = pyqtSignal()
    searchFinished = pyqtSignal()

    def __init__(self, finders, iface, parent=None):
        self.iface = iface
        self.mapCanvas = iface.mapCanvas()
        self.rubber = QgsRubberBand(self.mapCanvas)
        self.rubber.setColor(QColor(255, 255, 50, 200))
        self.rubber.setIcon(self.rubber.ICON_CIRCLE)
        self.rubber.setIconSize(15)
        self.rubber.setWidth(4)
        self.rubber.setBrushStyle(Qt.NoBrush)

        QComboBox.__init__(self, parent)
        self.setEditable(True)
        self.setInsertPolicy(QComboBox.InsertAtTop)
        self.setMinimumHeight(27)
        self.setSizePolicy(QSizePolicy.Expanding,
                           QSizePolicy.Fixed)

        self.insertSeparator(0)
        self.lineEdit().returnPressed.connect(self.search)

        self.resultView = QTreeView()
        self.resultView.setHeaderHidden(True)
        self.resultView.setMinimumHeight(300)
        self.resultView.activated.connect(self.itemActivated)
        self.resultView.pressed.connect(self.itemPressed)
        self.setView(self.resultView)

        self.resultModel = ResultModel(self)
        self.setModel(self.resultModel)

        self.finders = finders
        for finder in self.finders.values():
            finder.resultFound.connect(self.resultFound)
            finder.limitReached.connect(self.limitReached)
            finder.finished.connect(self.finished)

        self.clearButton = QPushButton(self)
        self.clearButton.setIcon(QIcon(":/plugins/quickfinder/icons/draft.svg"))
        self.clearButton.setText('')
        self.clearButton.setFlat(True)
        self.clearButton.setCursor(QCursor(Qt.ArrowCursor))
        self.clearButton.setStyleSheet('border: 0px; padding: 0px;')
        self.clearButton.clicked.connect(self.clear)

        layout = QHBoxLayout(self)
        self.setLayout(layout)
        layout.addStretch()
        layout.addWidget(self.clearButton)
        layout.addSpacing(20)

        buttonSize = self.clearButton.sizeHint()
        # frameWidth = self.lineEdit().style().pixelMetric(QtGui.QStyle.PM_DefaultFrameWidth)
        padding = buttonSize.width()  # + frameWidth + 1
        self.lineEdit().setStyleSheet('QLineEdit {padding-right: %dpx; }' % padding)

    def __del__(self):
        if self.rubber:
            self.iface.mapCanvas().scene().removeItem(self.rubber)
            del self.rubber

    def clearSelection(self):
        self.resultModel.setSelected(None, self.resultView.palette())
        self.rubber.reset()

    def clear(self):
        self.clearSelection()
        self.resultModel.clearResults()
        self.lineEdit().setText('')

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Escape:
            self.clearSelection()
        QComboBox.keyPressEvent(self, event)

    def search(self):
        if self.running:
            return

        toFind = self.lineEdit().text()
        if not toFind or toFind == '':
            return

        self.running = True
        self.searchStarted.emit()

        self.clearSelection()
        self.resultModel.clearResults()
        self.resultModel.truncateHistory(MySettings().value("historyLength"))
        self.resultModel.setLoading(True)
        self.showPopup()

        QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)

        self.findersToStart = []
        for finder in self.finders.values():
            if finder.activated():
                self.findersToStart.append(finder)

        bbox = self.mapCanvas.fullExtent()

        while len(self.findersToStart) > 0:
            finder = self.findersToStart[0]
            self.findersToStart.remove(finder)
            self.resultModel.addResult(finder.name)
            finder.start(toFind, bbox=bbox)

        # For case there is no finder activated
        self.finished(None)

    def stop(self):
        self.findersToStart = []
        for finder in self.finders.values():
            if finder.isRunning():
                finder.stop()
        self.finished(None)

    def resultFound(self, finder, layername, value, geometry, srid):
        self.resultModel.addResult(finder.name, layername, value, geometry, srid)
        self.resultView.expandAll()

    def limitReached(self, finder, layername):
        self.resultModel.addEllipsys(finder.name, layername)

    def finished(self, finder):
        if len(self.findersToStart) > 0:
            return
        for finder in self.finders.values():
            if finder.isRunning():
                return

        self.running = False
        self.searchFinished.emit()

        self.resultModel.setLoading(False)

        QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)

    def itemActivated(self, index):
        item = self.resultModel.itemFromIndex(index)
        self.showItem(item)

    def itemPressed(self, index):
        item = self.resultModel.itemFromIndex(index)
        if QApplication.mouseButtons() == Qt.LeftButton:
            self.showItem(item)

    def showItem(self, item):
        if isinstance(item, ResultItem):
            self.resultModel.setSelected(item, self.resultView.palette())
            geometry = self.transformGeom(item)
            self.rubber.reset(geometry.type())
            self.rubber.setToGeometry(geometry, None)
            self.zoomToRubberBand()
            return

        if isinstance(item, GroupItem):
            child = item.child(0)
            if isinstance(child, ResultItem):
                self.resultModel.setSelected(item, self.resultView.palette())
                self.rubber.reset(child.geometry.type())
                for i in xrange(0, item.rowCount()):
                    geometry = self.transformGeom(item.child(i))
                    self.rubber.addGeometry(geometry, None)
                self.zoomToRubberBand()
            return

        if item.__class__.__name__ == 'QStandardItem':
            self.clearSelection()

    def transformGeom(self, item):
        src_crs = QgsCoordinateReferenceSystem()
        src_crs.createFromSrid(item.srid)
        dest_crs = self.mapCanvas.mapRenderer().destinationCrs()
        geom = QgsGeometry(item.geometry)
        geom.transform(QgsCoordinateTransform(src_crs, dest_crs))
        return geom

    def zoomToRubberBand(self):
        geom = self.rubber.asGeometry()
        if geom:
            rect = geom.boundingBox()
            rect.scale(1.5)
            self.mapCanvas.setExtent(rect)
            self.mapCanvas.refresh()
Example #12
0
class SoLocatorFilter(QgsLocatorFilter):

    HEADERS = {b'User-Agent': b'Mozilla/5.0 QGIS SoLocator Filter'}

    message_emitted = pyqtSignal(str, str, Qgis.MessageLevel, QWidget)

    def __init__(self, iface: QgisInterface = None):
        """"
        :param iface: QGIS interface, given when on the main thread (which will display/trigger results), None otherwise
        """
        super().__init__()

        self.iface = iface
        self.settings = Settings()

        #  following properties will only be used in main thread
        self.rubber_band = None
        self.map_canvas: QgsMapCanvas = None
        self.transform_ch = None
        self.current_timer = None
        self.result_found = False
        self.nam_fetch_feature = None

        if iface is not None:
            # happens only in main thread
            self.map_canvas = iface.mapCanvas()
            self.map_canvas.destinationCrsChanged.connect(
                self.create_transforms)

            self.rubber_band = QgsRubberBand(self.map_canvas,
                                             QgsWkbTypes.PolygonGeometry)
            self.rubber_band.setColor(QColor(255, 50, 50, 200))
            self.rubber_band.setFillColor(QColor(255, 255, 50, 160))
            self.rubber_band.setBrushStyle(Qt.SolidPattern)
            self.rubber_band.setLineStyle(Qt.SolidLine)
            self.rubber_band.setIcon(self.rubber_band.ICON_CIRCLE)
            self.rubber_band.setIconSize(15)
            self.rubber_band.setWidth(4)
            self.rubber_band.setBrushStyle(Qt.NoBrush)

            self.create_transforms()

    def name(self):
        return 'SoLocator'

    def clone(self):
        return SoLocatorFilter()

    def priority(self):
        return QgsLocatorFilter.Highest

    def displayName(self):
        return 'SoLocator'

    def prefix(self):
        return 'sol'

    def clearPreviousResults(self):
        if self.rubber_band:
            self.rubber_band.reset(QgsWkbTypes.PointGeometry)

        if self.current_timer is not None:
            self.current_timer.stop()
            self.current_timer.deleteLater()
            self.current_timer = None

    def hasConfigWidget(self):
        return True

    def openConfigWidget(self, parent=None):
        dlg = ConfigDialog(parent)
        dlg.exec_()

    def create_transforms(self):
        # this should happen in the main thread
        src_crs_ch = QgsCoordinateReferenceSystem('EPSG:2056')
        assert src_crs_ch.isValid()
        dst_crs = self.map_canvas.mapSettings().destinationCrs()
        self.transform_ch = QgsCoordinateTransform(src_crs_ch, dst_crs,
                                                   QgsProject.instance())

    def enabled_dataproducts(self):
        categories = DATA_PRODUCTS.keys()
        skipped = self.settings.value('skipped_dataproducts')
        return ','.join(list(filter(lambda id: id not in skipped, categories)))

    @staticmethod
    def url_with_param(url, params) -> str:
        url = QUrl(url)
        q = QUrlQuery(url)
        for key, value in params.items():
            q.addQueryItem(key, value)
        url.setQuery(q)
        return url.url()

    def fetchResults(self, search: str, context: QgsLocatorContext,
                     feedback: QgsFeedback):
        try:
            self.dbg_info("start solocator search...")

            if len(search) < 3:
                return

            self.result_found = False

            params = {
                'searchtext': str(search),
                'filter': self.enabled_dataproducts(),
                'limit': str(self.settings.value('results_limit'))
            }

            nam = NetworkAccessManager()
            feedback.canceled.connect(nam.abort)
            url = self.url_with_param(SEARCH_URL, params)
            self.dbg_info(url)
            try:
                (response, content) = nam.request(url,
                                                  headers=self.HEADERS,
                                                  blocking=True)
                self.handle_response(response, search)
            except RequestsExceptionUserAbort:
                pass
            except RequestsException as err:
                self.info(err, Qgis.Info)

            if not self.result_found:
                result = QgsLocatorResult()
                result.filter = self
                result.displayString = self.tr('No result found.')
                result.userData = NoResult
                self.resultFetched.emit(result)

        except Exception as e:
            self.info(e, Qgis.Critical)
            exc_type, exc_obj, exc_traceback = sys.exc_info()
            filename = os.path.split(
                exc_traceback.tb_frame.f_code.co_filename)[1]
            self.info(
                '{} {} {}'.format(exc_type, filename, exc_traceback.tb_lineno),
                Qgis.Critical)
            self.info(
                traceback.print_exception(exc_type, exc_obj, exc_traceback),
                Qgis.Critical)

    def data_product_qgsresult(self, data: dict, sub_layer: bool, score: float,
                               stacktype) -> QgsLocatorResult:
        result = QgsLocatorResult()
        result.filter = self
        result.displayString = '{prefix}{title}'.format(
            prefix=' ↳ ' if sub_layer else '', title=data['display'])
        if stacktype == 'background':
            result.group = 'Hintergrundkarten'
        else:
            loading_mode: LoadingMode = self.settings.value(
                'default_layer_loading_mode')
            result.group = 'Vordergrundkarten (Doppelklick: {normal}, Ctrl-Doppelklick: {alt})'.format(
                normal=loading_mode, alt=loading_mode.alternate_mode())
        result.userData = DataProductResult(
            type=data['type'],
            dataproduct_id=data['dataproduct_id'],
            display=data['display'],
            dset_info=data['dset_info'],
            stacktype=stacktype,
            sublayers=data.get('sublayers', None))
        data_product = 'dataproduct'
        data_type = data['type']
        result.icon, result.description = dataproduct2icon_description(
            data_product, data_type)
        result.score = score
        return result

    def handle_response(self, response, search_text: str):
        try:
            if response.status_code != 200:
                if not isinstance(response.exception,
                                  RequestsExceptionUserAbort):
                    self.info("Error in main response with status code: "
                              "{} from {}".format(response.status_code,
                                                  response.url))
                return

            data = json.loads(response.content.decode('utf-8'))

            # Since results are ordered by score (0 to 1)
            # we use an ordering score to keep the same order than the one from the remote service
            score = 1

            # sub-filtering
            # dbg_info(data['result_counts'])
            if len(data['result_counts']) > 1:
                for _filter in data['result_counts']:
                    result = QgsLocatorResult()
                    result.filter = self
                    result.group = 'Suche verfeinern'
                    result.displayString = _filter['filterword']
                    if _filter['count']:
                        result.displayString += ' ({})'.format(
                            _filter['count'])
                    self.dbg_info(_filter)
                    result.icon, _ = dataproduct2icon_description(
                        _filter['dataproduct_id'], 'datasetview')
                    result.userData = FilterResult(_filter['filterword'],
                                                   search_text)
                    result.score = score
                    self.resultFetched.emit(result)
                    score -= 0.001

            for res in data['results']:
                # dbg_info(res)

                result = QgsLocatorResult()
                result.filter = self

                if 'feature' in res.keys():
                    f = res['feature']
                    # dbg_info("feature: {}".format(f))
                    result.displayString = f['display']
                    result.group = 'Orte'
                    result.userData = FeatureResult(
                        dataproduct_id=f['dataproduct_id'],
                        id_field_name=f['id_field_name'],
                        id_field_type=f['id_field_type'],
                        feature_id=f['feature_id'])
                    data_product = f['dataproduct_id']
                    data_type = None
                    result.icon, result.description = dataproduct2icon_description(
                        data_product, data_type)
                    result.score = score
                    self.resultFetched.emit(result)
                    score -= 0.001

                elif 'dataproduct' in res.keys():
                    dp = res['dataproduct']
                    # self.dbg_info("data_product: {}".format(dp))
                    result = self.data_product_qgsresult(
                        dp, False, score, dp['stacktype'])
                    self.resultFetched.emit(result)
                    score -= 0.001

                    # also give sublayers
                    for layer in dp.get('sublayers', []):
                        always_show_sublayers = True
                        if always_show_sublayers or search_text.lower(
                        ) in layer['display'].lower():
                            result = self.data_product_qgsresult(
                                layer, True, score, dp['stacktype'])
                            self.resultFetched.emit(result)
                            score -= 0.001

                else:
                    continue

                self.result_found = True

        except Exception as e:
            self.info(str(e), Qgis.Critical)
            exc_type, exc_obj, exc_traceback = sys.exc_info()
            filename = os.path.split(
                exc_traceback.tb_frame.f_code.co_filename)[1]
            self.info(
                '{} {} {}'.format(exc_type, filename, exc_traceback.tb_lineno),
                Qgis.Critical)
            self.info(
                traceback.print_exception(exc_type, exc_obj, exc_traceback),
                Qgis.Critical)

    def triggerResult(self, result: QgsLocatorResult):
        # this is run in the main thread, i.e. map_canvas is not None
        self.clearPreviousResults()

        ctrl_clicked = Qt.ControlModifier == QApplication.instance(
        ).queryKeyboardModifiers()
        self.dbg_info(("CTRL pressed: {}".format(ctrl_clicked)))

        user_data = self.get_user_data(result)
        if type(user_data) == NoResult:
            pass
        elif type(user_data) == FilterResult:
            self.filtered_search(user_data)
        elif type(user_data) == FeatureResult:
            self.fetch_feature(user_data)
        elif type(user_data) == DataProductResult:
            self.fetch_data_product(user_data, ctrl_clicked)
        else:
            self.info('Incorrect result. Please contact support',
                      Qgis.Critical)

    def filtered_search(self, filter_result: FilterResult):
        search_text = '{prefix} {filter_word}: {search}'.format(
            prefix=self.activePrefix(),
            filter_word=filter_result.filter_word,
            search=filter_result.search)
        # Compatibility for QGIS < 3.10
        # TODO: remove
        try:
            self.iface.locatorSearch(search_text)
        except AttributeError:
            for w in self.iface.mainWindow().findChildren(QgsFilterLineEdit):
                if hasattr(w.parent(), 'search') and hasattr(
                        w.parent(), 'invalidateResults'):
                    w.setText(search_text)
                    w.parent().setFocus(True)
                    return
            raise NameError('Locator not found')

    def highlight(self, geometry: QgsGeometry):
        self.clearPreviousResults()
        if geometry is None:
            return

        self.rubber_band.reset(geometry.type())
        self.rubber_band.addGeometry(geometry, None)

        rect = geometry.boundingBox()
        if not self.settings.value('keep_scale'):
            if rect.isEmpty():
                current_extent = self.map_canvas.extent()
                rect = current_extent.scaled(
                    self.settings.value('point_scale') /
                    self.map_canvas.scale(), rect.center())
            else:
                rect.scale(4)
        self.map_canvas.setExtent(rect)
        self.map_canvas.refresh()

        self.current_timer = QTimer()
        self.current_timer.timeout.connect(self.clearPreviousResults)
        self.current_timer.setSingleShot(True)
        self.current_timer.start(5000)

    def fetch_feature(self, feature: FeatureResult):
        self.dbg_info(feature)
        url = '{url}/{dataset}/{id}'.format(url=FEATURE_URL,
                                            dataset=feature.dataproduct_id,
                                            id=feature.feature_id)
        self.nam_fetch_feature = NetworkAccessManager()
        self.dbg_info(url)
        self.nam_fetch_feature.finished.connect(self.parse_feature_response)
        self.nam_fetch_feature.request(url,
                                       headers=self.HEADERS,
                                       blocking=False)

    def parse_feature_response(self, response):
        if response.status_code != 200:
            if not isinstance(response.exception, RequestsExceptionUserAbort):
                self.info("Error in feature response with status code: "
                          "{} from {}".format(response.status_code,
                                              response.url))
            return

        data = json.loads(response.content.decode('utf-8'))
        self.dbg_info(data.keys())
        self.dbg_info(data['properties'])
        self.dbg_info(data['geometry'])
        self.dbg_info(data['crs'])
        self.dbg_info(data['type'])

        assert data['crs']['properties'][
            'name'] == 'urn:ogc:def:crs:EPSG::2056'

        geometry_type = data['geometry']['type']
        geometry = QgsGeometry()

        if geometry_type.lower() == 'point':
            geometry = QgsGeometry.fromPointXY(
                QgsPointXY(data['geometry']['coordinates'][0],
                           data['geometry']['coordinates'][1]))

        elif geometry_type.lower() == 'polygon':
            rings = data['geometry']['coordinates']
            for r in range(0, len(rings)):
                for p in range(0, len(rings[r])):
                    rings[r][p] = QgsPointXY(rings[r][p][0], rings[r][p][1])
            geometry = QgsGeometry.fromPolygonXY(rings)

        elif geometry_type.lower() == 'multipolygon':
            islands = data['geometry']['coordinates']
            for i in range(0, len(islands)):
                for r in range(0, len(islands[i])):
                    for p in range(0, len(islands[i][r])):
                        islands[i][r][p] = QgsPointXY(islands[i][r][p][0],
                                                      islands[i][r][p][1])
            geometry = QgsGeometry.fromMultiPolygonXY(islands)

        else:
            # SoLocator does not handle {geometry_type} yet. Please contact support
            self.info(
                'SoLocator unterstützt den Geometrietyp {geometry_type} nicht.'
                ' Bitte kontaktieren Sie den Support.'.format(
                    geometry_type=geometry_type), Qgis.Warning)

        geometry.transform(self.transform_ch)
        self.highlight(geometry)

    def fetch_data_product(self, product: DataProductResult,
                           alternate_mode: bool):
        self.dbg_info(product)
        url = '{url}/{dataproduct_id}'.format(
            url=DATA_PRODUCT_URL, dataproduct_id=product.dataproduct_id)
        self.nam_fetch_feature = NetworkAccessManager()
        self.dbg_info(url)
        is_background = product.stacktype == 'background'
        self.dbg_info('is_background {}'.format(is_background))
        self.nam_fetch_feature.finished.connect(
            lambda response: self.parse_data_product_response(
                response, is_background, alternate_mode))
        self.nam_fetch_feature.request(url,
                                       headers=self.HEADERS,
                                       blocking=False)

    def parse_data_product_response(self, response, is_background: bool,
                                    alternate_mode: bool):
        if response.status_code != 200:
            if not isinstance(response.exception, RequestsExceptionUserAbort):
                self.info("Error in feature response with status code: "
                          "{} from {}".format(response.status_code,
                                              response.url))
            return

        data = json.loads(response.content.decode('utf-8'))
        LayerLoader(data, self.iface, is_background, alternate_mode)

    def info(self, msg="", level=Qgis.Info):
        self.logMessage(str(msg), level)

    def dbg_info(self, msg=""):
        if DEBUG:
            self.info(msg)

    def get_user_data(self, result):
        if hasattr(result, 'getUserData'):
            return result.getUserData()
        else:
            return result.userData
Example #13
0
class ShLocatorFilter(QgsLocatorFilter):

    HEADERS = {b'User-Agent': b'Mozilla/5.0 QGIS ShLocator Filter'}

    message_emitted = pyqtSignal(str, str, Qgis.MessageLevel, QWidget)

    def __init__(self, iface: QgisInterface = None):
        """"
        :param iface: QGIS interface, given when on the main thread (which will display/trigger results), None otherwise
        """
        super().__init__()

        self.iface = iface
        self.settings = Settings()

        #  following properties will only be used in main thread
        self.rubber_band = None
        self.map_canvas: QgsMapCanvas = None
        self.transform_ch = None
        self.current_timer = None
        self.result_found = False
        self.nam_fetch_feature = None

        if iface is not None:
            # happens only in main thread
            self.map_canvas = iface.mapCanvas()
            self.map_canvas.destinationCrsChanged.connect(
                self.create_transforms)

            self.rubber_band = QgsRubberBand(
                self.map_canvas, QgsWkbTypes.PolygonGeometry)
            self.rubber_band.setColor(QColor(255, 50, 50, 200))
            self.rubber_band.setFillColor(QColor(255, 255, 50, 160))
            self.rubber_band.setBrushStyle(Qt.SolidPattern)
            self.rubber_band.setLineStyle(Qt.SolidLine)
            self.rubber_band.setIcon(self.rubber_band.ICON_CIRCLE)
            self.rubber_band.setIconSize(15)
            self.rubber_band.setWidth(4)
            self.rubber_band.setBrushStyle(Qt.NoBrush)

            self.create_transforms()

    def name(self):
        return 'ShLocator'

    def clone(self):
        return ShLocatorFilter()

    def priority(self):
        return QgsLocatorFilter.Highest

    def displayName(self):
        return 'ShLocator'

    def prefix(self):
        return 'shl'

    def clearPreviousResults(self):
        if self.rubber_band:
            self.rubber_band.reset(QgsWkbTypes.PointGeometry)

        if self.current_timer is not None:
            self.current_timer.stop()
            self.current_timer.deleteLater()
            self.current_timer = None

    def hasConfigWidget(self):
        return True

    def openConfigWidget(self, parent=None):
        dlg = ConfigDialog(parent)
        dlg.exec_()

    def create_transforms(self):
        # this should happen in the main thread
        src_crs_ch = QgsCoordinateReferenceSystem('EPSG:2056')
        assert src_crs_ch.isValid()
        dst_crs = self.map_canvas.mapSettings().destinationCrs()
        self.transform_ch = QgsCoordinateTransform(
            src_crs_ch, dst_crs, QgsProject.instance())

    @staticmethod
    def url_with_param(url, params) -> str:
        url = QUrl(url)
        q = QUrlQuery(url)
        for key, value in params.items():
            q.addQueryItem(key, value)
        url.setQuery(q)
        return url.url()

    def fetchResults(self, search: str, context: QgsLocatorContext, feedback: QgsFeedback):
        try:
            dbg_info("start shlocator search...")

            if len(search) < 3:
                return

            self.result_found = False

            params = {
                'query': str(search),
                'maxfeatures': str(self.settings.value('max_features'))
            }

            nam = NetworkAccessManager()
            feedback.canceled.connect(nam.abort)
            url = self.url_with_param(
                self.settings.value('service_url'), params)
            dbg_info(url)
            try:
                (response, content) = nam.request(
                    url, headers=self.HEADERS, blocking=True)
                self.handle_response(response, search)
            except RequestsExceptionUserAbort:
                pass
            except RequestsException as err:
                info(err, Qgis.Info)

            if not self.result_found:
                result = QgsLocatorResult()
                result.filter = self
                result.displayString = self.tr('No result found.')
                result.userData = NoResult
                self.resultFetched.emit(result)

        except Exception as e:
            info(e, Qgis.Critical)
            exc_type, exc_obj, exc_traceback = sys.exc_info()
            filename = os.path.split(
                exc_traceback.tb_frame.f_code.co_filename)[1]
            info('{} {} {}'.format(exc_type, filename,
                                   exc_traceback.tb_lineno), Qgis.Critical)
            info(traceback.print_exception(
                exc_type, exc_obj, exc_traceback), Qgis.Critical)

    def data_product_qgsresult(self, data: dict) -> QgsLocatorResult:
        result = QgsLocatorResult()
        result.filter = self
        result.displayString = data['display']
        result.group = 'Karten'
        result.userData = DataProductResult(
            type=data['type'],
            dataproduct_id=data['dataproduct_id'],
            display=data['display'],
            dset_info=data['dset_info'],
            sublayers=data.get('sublayers', None)
        )
        data_product = 'dataproduct'
        data_type = data['type']
        result.icon, result.description = dataproduct2icon_description(
            data_product, data_type)
        return result

    def handle_response(self, response, search_text: str):
        try:
            if response.status_code != 200:
                if not isinstance(response.exception, RequestsExceptionUserAbort):
                    info("Error in main response with status code: "
                         "{} from {}".format(response.status_code, response.url))
                return

            display_name_field = QgsField('display_name', QVariant.String)
            fields = QgsFields()
            fields.append(display_name_field)
            features = QgsJsonUtils.stringToFeatureList(response.content.decode('utf-8'), fields, QTextCodec.codecForName('UTF-8'))
            dbg_info('Found {} features'.format(len(features)))
            dbg_info('Data {}'.format(response.content.decode('utf-8')))

            for feature in features:
                dbg_info('Adding feature {}'.format(feature['display_name']))
                result = QgsLocatorResult()
                result.filter = self
                result.group = 'Objekte'
                result.displayString = feature['display_name']
                result.userData = FeatureResult(feature)
                self.resultFetched.emit(result)

                self.result_found = True

        except Exception as e:
            info(str(e), Qgis.Critical)
            exc_type, exc_obj, exc_traceback = sys.exc_info()
            filename = os.path.split(
                exc_traceback.tb_frame.f_code.co_filename)[1]
            info('{} {} {}'.format(exc_type, filename,
                                   exc_traceback.tb_lineno), Qgis.Critical)
            info(traceback.print_exception(
                exc_type, exc_obj, exc_traceback), Qgis.Critical)

    def triggerResult(self, result: QgsLocatorResult):
        # this is run in the main thread, i.e. map_canvas is not None
        self.clearPreviousResults()

        if type(result.userData) == NoResult:
            pass
        elif type(result.userData) == FeatureResult:
            self.highlight(result.userData.feature.geometry())
        else:
            info('Incorrect result. Please contact support', Qgis.Critical)

    def highlight(self, geometry: QgsGeometry):
        self.rubber_band.reset(geometry.type())
        self.rubber_band.addGeometry(geometry, None)

        rect = geometry.boundingBox()
        if not self.settings.value('keep_scale'):
            if rect.isEmpty():
                current_extent = self.map_canvas.extent()
                rect = current_extent.scaled(self.settings.value(
                    'point_scale')/self.map_canvas.scale(), rect.center())
            else:
                rect.scale(4)
        self.map_canvas.setExtent(rect)
        self.map_canvas.refresh()

#        self.current_timer = QTimer()
#        self.current_timer.timeout.connect(self.clearPreviousResults)
#        self.current_timer.setSingleShot(True)
#        self.current_timer.start(5000)

    def parse_feature_response(self, response):
        if response.status_code != 200:
            if not isinstance(response.exception, RequestsExceptionUserAbort):
                info("Error in feature response with status code: "
                     "{} from {}".format(response.status_code, response.url))
            return

        data = json.loads(response.content.decode('utf-8'))

        geometry_type = data['geometry']['type']
        geometry = QgsGeometry()

        if geometry_type == 'Point':
            geometry = QgsGeometry.fromPointXY(QgsPointXY(data['geometry']['coordinates'][0],
                                                          data['geometry']['coordinates'][1]))

        elif geometry_type == 'Polygon':
            rings = data['geometry']['coordinates']
            for r in range(0, len(rings)):
                for p in range(0, len(rings[r])):
                    rings[r][p] = QgsPointXY(rings[r][p][0], rings[r][p][1])
            geometry = QgsGeometry.fromPolygonXY(rings)

        elif geometry_type == 'MultiPolygon':
            islands = data['geometry']['coordinates']
            for i in range(0, len(islands)):
                for r in range(0, len(islands[i])):
                    for p in range(0, len(islands[i][r])):
                        islands[i][r][p] = QgsPointXY(
                            islands[i][r][p][0], islands[i][r][p][1])
            geometry = QgsGeometry.fromMultiPolygonXY(islands)

        else:
            # ShLocator does not handle {geometry_type} yet. Please contact support
            info('ShLocator unterstützt den Geometrietyp {geometry_type} nicht.'
                 ' Bitte kontaktieren Sie den Support.'.format(geometry_type=geometry_type), Qgis.Warning)

        geometry.transform(self.transform_ch)
        self.highlight(geometry)
Example #14
0
class teamqgisDock(QDockWidget, Ui_teamqgis):
    dockRemoved = pyqtSignal(str)

    def __init__(self, iface, layer, currentFeature):
        self.iface = iface
        self.layer = layer
        self.proj = QgsProject.instance()
        self.renderer = self.iface.mapCanvas().mapRenderer()
        self.settings = MySettings()
        QDockWidget.__init__(self)
        self.setupUi(self)

        # Track attr warnings so they are not repeated for multiple items
        self.warned_attr_values = []

        self.setWindowTitle("teamqgis: %s" % layer.name())
        if layer.hasGeometryType() is False:
            self.panCheck.setChecked(False)
            self.panCheck.setEnabled(False)
            self.scaleCheck.setChecked(False)
            self.scaleCheck.setEnabled(False)

        self.previousButton.setArrowType(Qt.LeftArrow)
        self.nextButton.setArrowType(Qt.RightArrow)
        icon = QIcon(":/plugins/teamqgis/icons/openform.svg")
        self.editFormButton.setIcon(icon)

        # actions
        icon = QIcon(":/plugins/teamqgis/icons/action.svg")
        self.actionButton.setIcon(icon)
        self.attrAction = layer.actions()
        actions = [self.attrAction[i] for i in range(self.attrAction.size())]
        preferredAction = layer.customProperty("teamqgisPreferedAction", "")
        if preferredAction not in actions:
            dfltAction = self.attrAction.defaultAction()
            if dfltAction > len(actions):
                preferredAction = self.attrAction[dfltAction].name()
        preferredActionFound = False
        for i, action in enumerate(actions):
            qAction = QAction(QIcon(":/plugins/teamqgis/icons/action.svg"), action.name(), self)
            qAction.triggered.connect(lambda: self.doAction(i))
            self.actionButton.addAction(qAction)
            if action.name() == preferredAction:
                self.actionButton.setDefaultAction(qAction)
                preferredActionFound = True
        if len(actions) == 0:
            self.actionButton.setEnabled(False)
        elif not preferredActionFound:
            self.actionButton.setDefaultAction(self.actionButton.actions()[0])

        self.nameComboBoxes = [self.fieldOneNameComboBox, self.fieldTwoNameComboBox, self.fieldThreeNameComboBox]
        self.valueComboBoxes = [self.fieldOneValueComboBox, self.fieldTwoValueComboBox, self.fieldThreeValueComboBox]

        self.updateNameComboBoxes()

        # Restore saved nameComboBox current indices if they exist
        for nameComboBox in self.nameComboBoxes:
            fieldName = self.layer.customProperty("teamqgis" + nameComboBox.objectName())
            if fieldName != None:
                nameComboBox.setCurrentIndex(nameComboBox.findText(fieldName))

        self.rubber = QgsRubberBand(self.iface.mapCanvas())
        self.selectionChanged()
        if currentFeature == self.listCombo.currentIndex():
            self.on_listCombo_currentIndexChanged(currentFeature)
        else:
            self.listCombo.setCurrentIndex(currentFeature)
        self.layer.layerDeleted.connect(self.close)
        self.layer.selectionChanged.connect(self.selectionChanged)
        self.layer.layerModified.connect(self.layerChanged)
        self.layer.editingStopped.connect(self.editingStopped)
        self.layer.editingStarted.connect(self.editingStarted)

        QObject.connect(self.proj, SIGNAL("allowedClassesChanged()"), self.updateValueComboBoxes)

    def updateNameComboBoxes(self):
        fieldNameMap = self.layer.dataProvider().fieldNameMap()
        allFields = fieldNameMap.keys()
        if 'ID' in allFields: allFields.remove('ID')
        if 'FID' in allFields: allFields.remove('FID')
        for nameComboBox in self.nameComboBoxes:
            nameComboBox.clear()
            nameComboBox.addItems(allFields)

    def updateValueComboBoxes(self):
        feature = self.getCurrentItem()
        for (valueComboBox, nameComboBox) in zip(self.valueComboBoxes, 
                self.nameComboBoxes):
            valueComboBox.clear()
            allowedClasses, hasAllowedClasses = self.proj.readListEntry("teamqgis", "allowedClasses")
            if hasAllowedClasses:
                valueComboBox.addItems(allowedClasses)
            attr_value = str(feature[nameComboBox.currentText()])
            if (allowedClasses == None) or (attr_value not in allowedClasses) and (attr_value not in self.warned_attr_values):
                self.iface.messageBar().pushMessage("Class name not in allowed class list",
                    'Assign an allowed class or add "%s" to allowed class list'%attr_value,
                    level=QgsMessageBar.WARNING, duration=3)
                self.warned_attr_values.append(attr_value)
                valueComboBox.addItem(attr_value)
            valueComboBox.setCurrentIndex(valueComboBox.findText(attr_value))

    def setRubber(self, feature):
        self.rubber.setColor(self.settings.value("rubberColor"))
        self.rubber.setWidth(self.settings.value("rubberWidth"))
        ##self.rubber.setLineStyle(Qt.DotLine)
        self.rubber.setBrushStyle(Qt.NoBrush)
        self.rubber.setToGeometry(feature.geometry(), self.layer)

    def closeEvent(self, e):
        self.rubber.reset()
        self.layer.layerDeleted.disconnect(self.close)
        self.layer.selectionChanged.disconnect(self.selectionChanged)
        self.layer.layerModified.disconnect(self.layerChanged)
        self.layer.editingStopped.disconnect(self.editingStopped)
        self.layer.editingStarted.disconnect(self.editingStarted)
        if self.settings.value("saveSelectionInProject"):
            self.layer.setCustomProperty("teamqgisSelection", repr([]))
        self.dockRemoved.emit(self.layer.id())
          
    def selectionChanged(self):
        self.cleanBrowserFields()
        self.rubber.reset()
        nItems = self.layer.selectedFeatureCount()
        if nItems < 1:
            self.close()
            self.layer.emit(SIGNAL("browserNoItem()"))
            return
        self.browseFrame.setEnabled(True)
        self.subset = self.layer.selectedFeaturesIds()
        if self.settings.value("saveSelectionInProject"):
            self.layer.setCustomProperty("teamqgisSelection", repr(self.subset))
        for fid in self.subset:
            self.listCombo.addItem("%u" % fid)
        self.setRubber(self.getCurrentItem())
        self.updateValueComboBoxes()

    def layerChanged(self):
        self.applyChangesButton.setEnabled(True)

    def editingStarted(self):
        for valueComboBox in self.valueComboBoxes:
            valueComboBox.setEnabled(True)
        self.translateRightButton.setEnabled(True)
        self.translateLeftButton.setEnabled(True)
        self.translateUpButton.setEnabled(True)
        self.translateDownButton.setEnabled(True)
        self.editFormButton.setDown(True)

    def editingStopped(self):
        self.applyChangesButton.setEnabled(False)
        for valueComboBox in self.valueComboBoxes:
            valueComboBox.setEnabled(False)
        self.translateRightButton.setEnabled(False)
        self.translateLeftButton.setEnabled(False)
        self.translateUpButton.setEnabled(False)
        self.translateDownButton.setEnabled(False)
        self.editFormButton.setDown(False)

    def cleanBrowserFields(self):
        self.currentPosLabel.setText('0/0')
        self.listCombo.clear()
          
    def panScaleToItem(self, feature):
        if self.panCheck.isChecked() is False:
            return
        featBobo = feature.geometry().boundingBox()
        # if scaling and bobo has width and height (i.e. not a point)
        if self.scaleCheck.isChecked() and featBobo.width() != 0 and featBobo.height() != 0:
            featBobo.scale(self.settings.value("scale"))
            ul = self.renderer.layerToMapCoordinates(self.layer, QgsPoint(featBobo.xMinimum(), featBobo.yMaximum()))
            ur = self.renderer.layerToMapCoordinates(self.layer, QgsPoint(featBobo.xMaximum(), featBobo.yMaximum()))
            ll = self.renderer.layerToMapCoordinates(self.layer, QgsPoint(featBobo.xMinimum(), featBobo.yMinimum()))
            lr = self.renderer.layerToMapCoordinates(self.layer, QgsPoint(featBobo.xMaximum(), featBobo.yMinimum()))
            x = (ul.x(), ur.x(), ll.x(), lr.x())
            y = (ul.y(), ur.y(), ll.y(), lr.y())
            x0 = min(x)
            y0 = min(y)
            x1 = max(x)
            y1 = max(y)
        else:
            panTo = self.renderer.layerToMapCoordinates(self.layer, featBobo.center())
            mapBobo = self.iface.mapCanvas().extent()
            xshift = panTo.x() - mapBobo.center().x()
            yshift = panTo.y() - mapBobo.center().y()
            x0 = mapBobo.xMinimum() + xshift
            y0 = mapBobo.yMinimum() + yshift
            x1 = mapBobo.xMaximum() + xshift
            y1 = mapBobo.yMaximum() + yshift
        self.iface.mapCanvas().setExtent(QgsRectangle(x0, y0, x1, y1))
        self.iface.mapCanvas().refresh()

    def getCurrentItem(self):
        i = self.listCombo.currentIndex()
        if i == -1:
            return None
        f = QgsFeature()
        if self.layer.getFeatures(QgsFeatureRequest().setFilterFid(self.subset[i])).nextFeature(f):
            return f
        else:
            raise NameError("feature not found")

    def doAction(self, i):
        f = self.getCurrentItem()
        self.actionButton.setDefaultAction(self.actionButton.actions()[i])
        self.layer.setCustomProperty("teamqgisPreferedAction", self.attrAction[i].name())
        self.attrAction.doActionFeature(i, f)

    def doTranslate(self, trans):
        # Based on the "doaffine" function in the qgsAffine plugin
        if (self.layer.geometryType() == 2):
            start=1
        else:
            start=0
        if (not self.layer.isEditable()):
            self.iface.messageBar().pushMessage("Layer not in edit mode",
                    'Select a vector layer and choose "Toggle Editing"', level=QgsMessageBar.WARNING)
        else:
            feature = self.getCurrentItem()
            result = feature.geometry()
            i = start
            vertex = result.vertexAt(i)
            fid = feature.id()
            while (vertex != QgsPoint(0, 0)):
                newx = vertex.x() + trans[0] * float(self.settings.value("xres"))
                newy = vertex.y() + trans[1] * float(self.settings.value("yres"))
                result.moveVertex(newx, newy, i)
                i += 1
                vertex = result.vertexAt(i)
            self.layer.changeGeometry(fid, result)
            self.iface.mapCanvas().refresh()
            self.rubber.reset()
            self.setRubber(feature)

    def changeAttribute(self, i, fieldNameComboBox, fieldValueComboBox):
        fieldValueComboBox.setCurrentIndex(i)
        feature = self.getCurrentItem()
        attr_index = self.layer.dataProvider().fieldNameMap()[fieldNameComboBox.currentText()]
        self.layer.changeAttributeValue(feature.id(), attr_index, 
                fieldValueComboBox.currentText())
        self.iface.mapCanvas().refresh()
        self.updateValueComboBoxes()

    def nameComboBox_activated(self, i, nameComboBox):
        self.layer.setCustomProperty('teamqgis' + nameComboBox.objectName(), nameComboBox.currentText())

    @pyqtSlot(name="on_previousButton_clicked")
    def previousFeature(self):
        i = self.listCombo.currentIndex()
        n = max(0, i-1)
        self.listCombo.setCurrentIndex(n)
        self.saveCurrentFeature(n)

    @pyqtSlot(name="on_nextButton_clicked")
    def nextFeature(self):
        self.warned_attr_values = [] # Reset attr warnings
        i = self.listCombo.currentIndex()
        c = self.listCombo.count()
        n = min(i+1, c-1)
        self.listCombo.setCurrentIndex(n)
        self.saveCurrentFeature(n)

    @pyqtSlot(int, name="on_listCombo_activated")
    def saveCurrentFeature(self, i):
        if self.settings.value("saveSelectionInProject"):
            self.layer.setCustomProperty("teamqgisCurrentItem", i)

    @pyqtSlot(int, name="on_fieldOneNameComboBox_activated")
    def fieldOneNameComboBox_activated(self, i):
        self.nameComboBox_activated(i, self.fieldOneNameComboBox)

    @pyqtSlot(int, name="on_fieldTwoNameComboBox_activated")
    def fieldTwoNameComboBox_activated(self, i):
        self.nameComboBox_activated(i, self.fieldTwoNameComboBox)

    @pyqtSlot(int, name="on_fieldThreeNameComboBox_activated")
    def fieldThreeNameComboBox_activated(self, i):
        self.nameComboBox_activated(i, self.fieldThreeNameComboBox)

    @pyqtSlot(int, name="on_listCombo_currentIndexChanged")
    def on_listCombo_currentIndexChanged(self, i):
        feature = self.getCurrentItem()
        if feature is None: 
            return
        self.rubber.reset()
        if self.listCombo.count() > 1:
            self.setRubber(feature)
            self.updateValueComboBoxes()
        # scale to feature
        self.panScaleToItem(feature)
        # Update browser
        self.currentPosLabel.setText("%u/%u" % (i+1, len(self.subset)))
        # emit signal
        self.layer.emit(SIGNAL("browserCurrentItem(long)"), feature.id())
          
    @pyqtSlot(int, name="on_panCheck_stateChanged")
    def on_panCheck_stateChanged(self, i):
        if self.panCheck.isChecked():
            self.scaleCheck.setEnabled(True)
            feature = self.getCurrentItem()
            if feature is None:
                return
            self.panScaleToItem(feature)
        else:
            self.scaleCheck.setEnabled(False)
               
    @pyqtSlot(int, name="on_scaleCheck_stateChanged")
    def on_scaleCheck_stateChanged(self, i):
        if self.scaleCheck.isChecked():
            feature = self.getCurrentItem()
            if feature is None: 
                return
            self.panScaleToItem(feature)

    @pyqtSlot(name="on_editFormButton_clicked")
    def openFeatureForm(self):
        if (self.layer.isEditable()):
            self.layer.commitChanges()
        else:
            self.layer.startEditing()

    @pyqtSlot(name="on_translateRightButton_clicked")
    def doTranslateRight(self):
        self.doTranslate((1, 0))

    @pyqtSlot(name="on_translateLeftButton_clicked")
    def doTranslateLeft(self):
        self.doTranslate((-1, 0))

    @pyqtSlot(name="on_translateUpButton_clicked")
    def doTranslateUp(self):
        self.doTranslate((0, 1))

    @pyqtSlot(name="on_translateDownButton_clicked")
    def doTranslateDown(self):
        self.doTranslate((0, -1))

    @pyqtSlot(name="on_applyChangesButton_clicked")
    def applyChanges(self):
        self.layer.commitChanges()
        self.layer.startEditing()
        self.layer.updateExtents()
        self.iface.mapCanvas().refresh()

    @pyqtSlot(int, name="on_fieldOneValueComboBox_activated")
    def on_fieldOneValueComboBox_activated(self, i):
        self.changeAttribute(i, self.fieldOneNameComboBox, self.fieldOneValueComboBox)

    @pyqtSlot(int, name="on_fieldTwoValueComboBox_activated")
    def on_fieldTwoValueComboBox_activated(self, i):
        self.changeAttribute(i, self.fieldTwoNameComboBox, self.fieldTwoValueComboBox)

    @pyqtSlot(int, name="on_fieldThreeValueComboBox_activated")
    def on_fieldThreeValueComboBox_activated(self, i):
        self.changeAttribute(i, self.fieldThreeNameComboBox, self.fieldThreeValueComboBox)
Example #15
0
class CopyTimeZoneTool(QgsMapToolEmitPoint):
    '''Class to interact with the map canvas to capture the coordinate
    when the mouse button is pressed and to display the coordinate in
    in the status bar.'''
    tzf = None
    last_tz = None

    def __init__(self, settings, iface):
        QgsMapToolEmitPoint.__init__(self, iface.mapCanvas())
        self.iface = iface
        self.settings = settings
        self.canvas = iface.mapCanvas()

        # Set up a polygon rubber band
        self.rubber = QgsRubberBand(self.canvas)
        self.rubber.setColor(QColor(255, 70, 0, 200))
        self.rubber.setWidth(3)
        self.rubber.setBrushStyle(Qt.NoBrush)

    def activate(self):
        '''When activated set the cursor to a crosshair.'''
        self.canvas.setCursor(Qt.CrossCursor)
        self.tzf = tzf_instance.getTZF()
        self.settings.show()

    def deactivate(self):
        self.last_tz = None
        self.rubber.reset()

    def formatMessage(self, pt):
        '''Format the coordinate string according to the settings from
        the settings dialog.'''
        # Make sure the coordinate is transformed to EPSG:4326
        canvasCRS = self.canvas.mapSettings().destinationCrs()
        if canvasCRS == epsg4326:
            pt4326 = pt
        else:
            transform = QgsCoordinateTransform(canvasCRS, epsg4326,
                                               QgsProject.instance())
            pt4326 = transform.transform(pt.x(), pt.y())
        try:
            tz_name = self.tzf.timezone_at(lng=pt4326.x(), lat=pt4326.y())
            mode = self.settings.mode()
            if mode == 1:
                msg = tz_name
            else:
                tz = timezone(tz_name)
                date = self.settings.date()
                loc_dt = tz.localize(
                    datetime(date.year(), date.month(), date.day()))
                offset = loc_dt.strftime('%z')
                if mode == 0:
                    msg = '{} ({})'.format(tz_name, offset)
                else:
                    msg = offset
        except Exception:
            # traceback.print_exc()
            msg = ''
            tz_name = ''
        if not msg:
            msg = ''
            tz_name = ''

        return (tz_name, msg)

    def canvasMoveEvent(self, event):
        '''Capture the coordinate as the user moves the mouse over
        the canvas. Show it in the status bar.'''
        pt = event.mapPoint()
        tz_name, msg = self.formatMessage(pt)
        if tz_name != self.last_tz:
            self.rubber.reset()
            self.last_tz = tz_name
            if tz_name != '':
                polygon = self.tzf.get_geometry(tz_name=tz_name)
                qgs_poly = tzf_to_qgis_polygon(polygon)
                canvasCRS = self.canvas.mapSettings().destinationCrs()
                if epsg4326 != canvasCRS:
                    to_canvas_crs = QgsCoordinateTransform(
                        epsg4326, canvasCRS, QgsProject.instance())
                    qgs_poly.transform(to_canvas_crs)
                self.rubber.addGeometry(qgs_poly, None)
                self.rubber.show()
        self.iface.statusBarIface().showMessage(msg, 4000)

    def canvasReleaseEvent(self, event):
        '''Capture the coordinate when the mouse button has been released,
        format it, and copy it to the clipboard. pt is QgsPointXY'''
        pt = event.mapPoint()

        tz_name, msg = self.formatMessage(pt)
        if msg:
            clipboard = QApplication.clipboard()
            clipboard.setText(msg)
            self.iface.messageBar().pushMessage(
                "",
                "'{}' copied to the clipboard".format(msg),
                level=Qgis.Info,
                duration=2)
Example #16
0
class SelectMapTool(QgsMapToolEmitPoint):
    features_selected_signal = pyqtSignal()
    selected_feature_ids = list()

    def __init__(self, canvas, layer, multi=True):
        self.canvas = canvas
        QgsMapToolEmitPoint.__init__(self, self.canvas)
        self._multi = multi
        self._layer = layer

        # Cursor
        cursor = QCursor()
        cursor.setShape(Qt.CrossCursor)
        self.setCursor(cursor)

        # RubberBand Style
        self.rubberBand = QgsRubberBand(self.canvas, True)
        self.rubberBand.setBrushStyle(Qt.Dense4Pattern)
        self.rubberBand.setColor(QColor(255, 181, 92))
        self.rubberBand.setStrokeColor(QColor(232, 137, 72))
        self.rubberBand.setWidth(0.2)
        self.reset()

    def reset(self):
        self.startPoint = self.endPoint = None
        self.isEmittingPoint = False
        self.rubberBand.reset(True)

    def canvasPressEvent(self, e):
        if e.button() & Qt.LeftButton:
            self.startPoint = self.toMapCoordinates(e.pos())
            self.endPoint = self.startPoint
            self.isEmittingPoint = True

    def canvasReleaseEvent(self, e):
        if e.button() & Qt.LeftButton:
            self.isEmittingPoint = False
            self.show_rubber_band()

            if self._multi and (e.modifiers() & Qt.ControlModifier
                                or e.modifiers() & Qt.ShiftModifier):
                if self.rubberBand.asGeometry().type(
                ) == QgsWkbTypes.PolygonGeometry:
                    self.reset()
                    return
                else:
                    id_features_intersect = self.ids_features_intersect()

                # if Control or Shift are selected, keeps the previous selection
                for id_feature_intersect in id_features_intersect:
                    # toggle selected feature
                    if id_feature_intersect not in self.selected_feature_ids:
                        self.selected_feature_ids.append(id_feature_intersect)
                    else:
                        self.selected_feature_ids.remove(id_feature_intersect)
            else:
                id_features_intersect = self.ids_features_intersect()
                # Clear selected features if Control or Shift are not selected
                self.selected_feature_ids = id_features_intersect

            self.select_features()
        elif e.button() & Qt.RightButton:
            self.features_selected_signal.emit()
            self.reset()

    def canvasDoubleClickEvent(self, e):
        self.canvasPressEvent(e)

    def canvasMoveEvent(self, e):
        if not self.isEmittingPoint:
            return
        self.endPoint = self.toMapCoordinates(e.pos())
        self.show_rubber_band()

    def show_rubber_band(self):
        self.create_rubber_band()
        if self.rubberBand:
            self.rubberBand.show()

    def create_rubber_band(self):
        self.rubberBand.reset(True)

        if self.startPoint is None and self.endPoint is None:
            return None
        elif self.startPoint.x() == self.endPoint.x() or self.startPoint.y(
        ) == self.endPoint.y():
            self.rubberBand.reset(QgsWkbTypes.PointGeometry)
            self.rubberBand.addPoint(self.startPoint,
                                     True)  # true to update canvas
        else:
            self.rubberBand.reset(QgsWkbTypes.PolygonGeometry)

            point1 = QgsPointXY(self.startPoint.x(), self.startPoint.y())
            point2 = QgsPointXY(self.startPoint.x(), self.endPoint.y())
            point3 = QgsPointXY(self.endPoint.x(), self.endPoint.y())
            point4 = QgsPointXY(self.endPoint.x(), self.startPoint.y())

            self.rubberBand.addPoint(point1, False)
            self.rubberBand.addPoint(point2, False)
            self.rubberBand.addPoint(point3, False)
            self.rubberBand.addPoint(point4, True)  # true to update canvas

            # Approximate polygon to point when it's too small related to the scale
            if (self.rubberBand.asGeometry().area() /
                    self.canvas.extent().area()) * 1000000 < 50:
                self.rubberBand.reset(QgsWkbTypes.PointGeometry)
                self.rubberBand.addPoint(self.startPoint,
                                         True)  # true to update canvas

    def ids_features_intersect(self):
        index = QgsSpatialIndex(self._layer)
        bbox = self.rubberBand.asGeometry().boundingBox()

        candidate_features = self._layer.getFeatures(index.intersects(bbox))
        geom = self.rubberBand.asGeometry()
        centroid = geom

        if not self._multi and geom.type() == QgsWkbTypes.PolygonGeometry:
            centroid = geom.centroid()

        features_selected = []
        distances_features_selected = dict()
        for candidate_feature in candidate_features:
            if candidate_feature.geometry().intersects(geom):
                features_selected.append(candidate_feature.id())
                # Calculate the distance to the centroid
                distances_features_selected[candidate_feature.id(
                )] = candidate_feature.geometry().distance(centroid)

        if not self._multi and geom.type() == QgsWkbTypes.PolygonGeometry:
            if features_selected:
                # Get the key corresponding to the minimum value within a dictionary
                features_selected = [
                    min(distances_features_selected,
                        key=distances_features_selected.get)
                ]

        return features_selected

    def select_features(self):
        # show features selected
        self._layer.selectByIds(self.selected_feature_ids)
        self.reset()

    def deactivate(self):
        QgsMapTool.deactivate(self)
        self.deactivated.emit()
Example #17
0
class QuadInstanceItemWidget(QFrame):

    quadSelected = pyqtSignal()

    def __init__(self, quad):
        QWidget.__init__(self)
        self.setMouseTracking(True)
        self.quad = quad
        self.nameLabel = QLabel(
            f'<b>{quad[ID]}</b><br><span style="color:grey;">'
            f'{quad[PERCENT_COVERED]} % covered</span>')
        self.iconLabel = QLabel()
        pixmap = QPixmap(PLACEHOLDER_THUMB, 'SVG')
        thumb = pixmap.scaled(48, 48, QtCore.Qt.KeepAspectRatio,
                              QtCore.Qt.SmoothTransformation)
        self.iconLabel.setPixmap(thumb)
        self.checkBox = QCheckBox("")
        self.checkBox.stateChanged.connect(self.check_box_state_changed)
        layout = QHBoxLayout()
        layout.setMargin(0)
        layout.addWidget(self.checkBox)
        vlayout = QVBoxLayout()
        vlayout.setMargin(0)
        vlayout.addWidget(self.iconLabel)
        self.iconWidget = QWidget()
        self.iconWidget.setFixedSize(48, 48)
        self.iconWidget.setLayout(vlayout)
        layout.addWidget(self.iconWidget)
        layout.addWidget(self.nameLabel)
        layout.addStretch()
        self.setLayout(layout)

        download_thumbnail(quad[LINKS][THUMBNAIL], self)

        self.footprint = QgsRubberBand(iface.mapCanvas(),
                                       QgsWkbTypes.PolygonGeometry)
        self.footprint.setFillColor(QUADS_AOI_COLOR)
        self.footprint.setStrokeColor(QUADS_AOI_COLOR)
        self.footprint.setWidth(2)

        self.footprintfill = QgsRubberBand(iface.mapCanvas(),
                                           QgsWkbTypes.PolygonGeometry)
        self.footprintfill.setFillColor(QUADS_AOI_BODY_COLOR)
        self.footprintfill.setWidth(0)

        self.update_footprint_brush()
        self.hide_solid_interior()
        self.show_footprint()

        self.setStyleSheet(
            "QuadInstanceItemWidget{border: 2px solid transparent;}")

    def set_thumbnail(self, img):
        pixmap = QPixmap(img)
        thumb = pixmap.scaled(48, 48, Qt.KeepAspectRatio,
                              Qt.SmoothTransformation)
        self.iconLabel.setPixmap(thumb)
        self.iconLabel.setStyleSheet("")

    def check_box_state_changed(self):
        self.update_footprint_brush()
        self.quadSelected.emit()

    def show_footprint(self):
        coords = self.quad[BBOX]
        extent = qgsrectangle_for_canvas_from_4326_bbox_coords(coords)
        self.geom = QgsGeometry.fromRect(extent)
        self.footprint.setToGeometry(self.geom)
        self.footprintfill.setToGeometry(self.geom)

    def hide_footprint(self):
        self.footprint.reset(QgsWkbTypes.PolygonGeometry)
        self.footprintfill.reset(QgsWkbTypes.PolygonGeometry)

    def show_solid_interior(self):
        self.footprintfill.setBrushStyle(Qt.SolidPattern)
        self.footprintfill.updateCanvas()

    def hide_solid_interior(self):
        self.footprintfill.setBrushStyle(Qt.NoBrush)
        self.footprintfill.updateCanvas()

    def update_footprint_brush(self):
        self.footprint.setBrushStyle(
            Qt.BDiagPattern if self.checkBox.isChecked() else Qt.NoBrush)
        self.footprint.updateCanvas()

    def remove_footprint(self):
        iface.mapCanvas().scene().removeItem(self.footprint)
        iface.mapCanvas().scene().removeItem(self.footprintfill)

    def isSelected(self):
        return self.checkBox.isChecked()

    def setChecked(self, checked, emit=True):
        if not emit:
            self.checkBox.blockSignals(True)
        self.checkBox.setChecked(checked)
        if not emit:
            self.update_footprint_brush()
            self.checkBox.blockSignals(False)

    def enterEvent(self, event):
        self.setStyleSheet(
            "QuadInstanceItemWidget{border: 2px solid rgb(157, 165, 0);}")
        self.show_solid_interior()

    def leaveEvent(self, event):
        self.setStyleSheet(
            "QuadInstanceItemWidget{border: 2px solid transparent;}")
        self.hide_solid_interior()
class GeomapfishLocatorFilter(QgsLocatorFilter):

    USER_AGENT = b'Mozilla/5.0 QGIS GeoMapFish Locator Filter'

    changed = pyqtSignal()

    def __init__(self, service: Service, iface: QgisInterface = None):
        super().__init__()
        self.service = service.clone()
        self.rubberband = None
        self.iface = None
        self.map_canvas = None
        self.current_timer = None
        self.settings = Settings()
        self.crs = QgsCoordinateReferenceSystem(self.service.crs)

        # only get map_canvas on main thread, not when cloning
        if iface is not None:
            self.iface = iface
            self.map_canvas = iface.mapCanvas()

            self.rubberband = QgsRubberBand(self.map_canvas)
            self.reset_rubberband()

    def name(self) -> str:
        return slugify(self.displayName())

    def clone(self):
        return GeomapfishLocatorFilter(self.service)

    def displayName(self) -> str:
        return self.service.name

    def prefix(self) -> str:
        return 'gmf'

    def hasConfigWidget(self) -> bool:
        return True

    def openConfigWidget(self, parent=None):
        cfg = FilterConfigurationDialog(self.service, parent)
        if cfg.exec_():
            self.service = cfg.service.clone()
            self.crs = QgsCoordinateReferenceSystem(self.service.crs)
            self.changed.emit()

    def reset_rubberband(self):
        # this should happen on main thread only!
        self.rubberband.setColor(self.settings.value('point_color'))
        self.rubberband.setIcon(self.rubberband.ICON_CIRCLE)
        self.rubberband.setIconSize(self.settings.value('point_size'))
        self.rubberband.setWidth(self.settings.value('line_width'))
        self.rubberband.setBrushStyle(Qt.NoBrush)

    @staticmethod
    def url_with_param(url, params) -> str:
        url = QUrl(url)
        q = QUrlQuery(url)
        for key, value in params.items():
            q.addQueryItem(key, value)
        url.setQuery(q)
        return url

    def emit_bad_configuration(self, err=None):
        result = QgsLocatorResult()
        result.filter = self
        result.displayString = self.tr('Locator filter is not configured.')
        result.description = err if err else self.tr(
            'Double-click to configure it.')
        result.userData = FilterNotConfigured
        result.icon = QgsApplication.getThemeIcon('mIconWarning.svg')
        self.resultFetched.emit(result)
        return

    @pyqtSlot()
    def clear_results(self):
        if self.rubberband:
            self.rubberband.reset(QgsWkbTypes.PointGeometry)
        if self.current_timer is not None:
            self.current_timer.timeout.disconnect(self.clear_results)
            self.current_timer.stop()
            self.current_timer.deleteLater()
            self.current_timer = None

    def fetchResults(self, search, context, feedback):
        self.dbg_info("start GMF locator search...")
        url = self.service.url
        if not url:
            self.emit_bad_configuration()
            return
        if len(search) < 2:
            return

        params = {
            'query': search,
            'limit': str(self.service.total_limit),
            'partitionlimit': str(self.service.category_limit)
        }
        url = self.url_with_param(url, params)
        self.dbg_info(url.url())

        _request = QNetworkRequest(url)
        _request.setRawHeader(b'User-Agent', self.USER_AGENT)
        request = QgsBlockingNetworkRequest()
        if self.service.authid:
            request.setAuthCfg(self.service.authid)

        response = request.get(_request, False, feedback)
        if response == QgsBlockingNetworkRequest.NoError:
            self.handle_response(request.reply().content())
        else:
            error_msg = request.reply().errorString()
            self.emit_bad_configuration(error_msg)
            self.info(error_msg, Qgis.Critical)
        self.finished.emit()

    def handle_response(self, content: QByteArray):
        try:
            data = json.loads(str(content.data(), encoding='utf-8'))
            # self.dbg_info(data)

            features = data['features']
            for f in features:
                json_geom = json.dumps(f['geometry'])
                ogr_geom = ogr.CreateGeometryFromJson(json_geom)
                wkt = ogr_geom.ExportToWkt()
                geometry = QgsGeometry.fromWkt(wkt)
                self.dbg_info('---------')
                self.dbg_info(
                    QgsWkbTypes.geometryDisplayString(geometry.type()))
                self.dbg_info(f.keys())
                self.dbg_info('{} {}'.format(f['properties']['layer_name'],
                                             f['properties']['label']))
                self.dbg_info(f['bbox'])
                self.dbg_info(f['geometry'])
                if geometry is None:
                    continue
                result = QgsLocatorResult()
                result.filter = self
                result.displayString = f['properties']['label']
                result.group = self.beautify_group(
                    f['properties']['layer_name'])
                result.userData = geometry
                self.resultFetched.emit(result)

        except Exception as e:
            self.info(str(e), Qgis.Critical)
            # exc_type, exc_obj, exc_traceback = sys.exc_info()
            # #filename = os.path.split(exc_traceback.tb_frame.f_code.co_filename)[1]
            # #self.info('{} {} {}'.format(exc_type, filename, exc_traceback.tb_lineno), Qgis.Critical)
            # self.info(traceback.print_exception(exc_type, exc_obj, exc_traceback), Qgis.Critical)

    def triggerResult(self, result):
        self.clear_results()
        if result.userData == FilterNotConfigured:
            self.openConfigWidget()
            self.iface.invalidateLocatorResults()
            return

        # this should be run in the main thread, i.e. mapCanvas should not be None
        geometry = result.userData
        # geometry.transform(self.transform)
        dbg_info(str(geometry.asWkt()))
        dbg_info(geometry.type())

        try:
            rect = QgsReferencedRectangle(geometry.boundingBox(), self.crs)
            rect.scale(4)
            self.map_canvas.setReferencedExtent(rect)
        except AttributeError:
            # QGIS < 3.10 handling
            from qgis.core import QgsCoordinateTransform, QgsProject
            transform = QgsCoordinateTransform(
                self.crs,
                self.map_canvas.mapSettings().destinationCrs(),
                QgsProject.instance())
            geometry.transform(transform)
            rect = geometry.boundingBox()
            rect.scale(4)
            self.map_canvas.setExtent(rect)

        self.map_canvas.refresh()

        if geometry.type() == QgsWkbTypes.PolygonGeometry:
            nflash = 16
            color1: QColor = self.settings.value('polygon_color')
            color2 = color1
            color1.setAlpha(200)
            color2.setAlpha(100)
            self.map_canvas.flashGeometries(
                [geometry], self.crs, color1, color2, nflash,
                self.settings.value('highlight_duration') / nflash * 1000)
        else:
            self.rubberband.reset(geometry.type())
            self.rubberband.addGeometry(geometry, self.crs)

            self.current_timer = QTimer()
            self.current_timer.timeout.connect(self.clear_results)
            self.current_timer.setSingleShot(True)
            self.current_timer.start(
                self.settings.value('highlight_duration') * 1000)

    def beautify_group(self, group) -> str:
        if self.service.remove_leading_digits:
            group = re.sub('^[0-9]+', '', group)
        if self.service.replace_underscore:
            group = group.replace("_", " ")
        if self.service.break_camelcase:
            group = self.break_camelcase(group)
        return group

    def info(self, msg="", level=Qgis.Info):
        QgsMessageLog.logMessage('{} {}'.format(self.__class__.__name__, msg),
                                 'QgsLocatorFilter', level)

    def dbg_info(self, msg=""):
        if DEBUG:
            self.info(msg)

    @staticmethod
    def break_camelcase(identifier) -> str:
        matches = re.finditer(
            '.+?(?:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|$)',
            identifier)
        return ' '.join([m.group(0) for m in matches])
Example #19
0
class ReverseGeocodeTool(QgsMapTool):
    def __init__(self, iface, settings):
        self.canvas = iface.mapCanvas()
        QgsMapTool.__init__(self, self.canvas)
        self.iface = iface
        self.settings = settings
        self.reverseGeoCodeDialog = ReverseGeocodeDialog(
            self, self.iface, self.iface.mainWindow())
        self.iface.addDockWidget(Qt.TopDockWidgetArea,
                                 self.reverseGeoCodeDialog)
        self.reverseGeoCodeDialog.hide()
        self.epsg4326 = QgsCoordinateReferenceSystem('EPSG:4326')
        self.marker = None

        # Set up a polygon/line rubber band
        self.rubber = QgsRubberBand(self.canvas)
        self.rubber.setColor(QColor(255, 70, 0, 200))
        self.rubber.setWidth(5)
        self.rubber.setBrushStyle(Qt.NoBrush)

    def activate(self):
        '''When activated set the cursor to a crosshair.'''
        self.canvas.setCursor(Qt.CrossCursor)
        self.show()

    def unload(self):
        self.iface.removeDockWidget(self.reverseGeoCodeDialog)
        self.reverseGeoCodeDialog = None
        if self.rubber:
            self.canvas.scene().removeItem(self.rubber)
            del self.rubber
        self.removeMarker()

    def addMarker(self, lat, lon):
        if self.marker:
            self.removeMarker()
        canvasCrs = self.canvas.mapSettings().destinationCrs()
        transform = QgsCoordinateTransform(self.epsg4326, canvasCrs,
                                           QgsProject.instance())
        center = transform.transform(lon, lat)
        self.marker = QgsVertexMarker(self.canvas)
        self.marker.setCenter(center)
        self.marker.setColor(QColor(255, 70, 0))
        self.marker.setIconSize(15)
        self.marker.setIconType(QgsVertexMarker.ICON_X)
        self.marker.setPenWidth(3)
        self.marker.show()

    def removeMarker(self):
        if self.marker:
            self.canvas.scene().removeItem(self.marker)
            self.marker = None

    def clearSelection(self):
        self.removeMarker()
        self.rubber.reset()

    def transform_geom(self, geometry):
        canvasCrs = self.canvas.mapSettings().destinationCrs()
        geom = QgsGeometry(geometry)
        geom.transform(
            QgsCoordinateTransform(self.epsg4326, canvasCrs,
                                   QgsProject.instance()))
        return geom

    def show(self):
        self.reverseGeoCodeDialog.show()

    def request(self, url):
        fetcher = QgsNetworkContentFetcher()
        fetcher.fetchContent(QUrl(url))
        evloop = QEventLoop()
        fetcher.finished.connect(evloop.quit)
        evloop.exec_(QEventLoop.ExcludeUserInputEvents)
        fetcher.finished.disconnect(evloop.quit)
        return fetcher.contentAsString()

    def canvasReleaseEvent(self, event):
        # Make sure the point is transfored to 4326
        self.clearSelection()
        pt = self.toMapCoordinates(event.pos())
        canvasCRS = self.canvas.mapSettings().destinationCrs()
        transform = QgsCoordinateTransform(canvasCRS, self.epsg4326,
                                           QgsProject.instance())
        pt = transform.transform(pt.x(), pt.y())
        url = '{}?format=json&lat={:f}&lon={:f}&zoom={:d}&addressdetails=0&polygon_text=1'.format(
            self.settings.reverseURL(), pt.y(), pt.x(),
            self.settings.levelOfDetail)
        # print( url )
        jsondata = self.request(url)

        try:
            jd = json.loads(jsondata)
            try:
                display_name = jd['display_name']
                self.setText(display_name)
            except KeyError:
                self.setText("[Could not find address]")
            try:
                wkt = jd['geotext']
                geometry = QgsGeometry.fromWkt(wkt)
                if geometry.wkbType() == QgsWkbTypes.Point:
                    pt = geometry.asPoint()
                    lon = pt.x()
                    lat = pt.y()
                    self.addMarker(lat, lon)
                else:
                    geometry = self.transform_geom(geometry)
                    self.rubber.addGeometry(geometry, None)
                    self.rubber.show()
            except KeyError:
                try:
                    lon = float(jd['lon'])
                    lat = float(jd['lat'])
                    self.addMarker(lat, lon)
                except:
                    pass
        except Exception:
            self.setText("Error: " + jsondata)

        if not self.reverseGeoCodeDialog.isVisible():
            self.show()

    def setText(self, text):
        self.reverseGeoCodeDialog.addressLineEdit.setText(text)
class GeomapfishLocatorFilter(QgsLocatorFilter):

    USER_AGENT = b'Mozilla/5.0 QGIS GeoMapFish Locator Filter'

    def __init__(self, iface: QgisInterface = None):
        super().__init__()
        self.rubber_band = None
        self.settings = Settings()
        self.iface = None
        self.map_canvas = None
        self.current_timer = None
        self.transform = None

        # only get map_canvas on main thread, not when cloning
        if iface is not None:
            self.iface = iface
            self.map_canvas = iface.mapCanvas()
            self.map_canvas.destinationCrsChanged.connect(
                self.create_transform)

            self.rubber_band = QgsRubberBand(self.map_canvas)
            self.rubber_band.setColor(QColor(255, 255, 50, 200))
            self.rubber_band.setIcon(self.rubber_band.ICON_CIRCLE)
            self.rubber_band.setIconSize(15)
            self.rubber_band.setWidth(4)
            self.rubber_band.setBrushStyle(Qt.NoBrush)

            self.create_transform()

    def name(self) -> str:
        return self.__class__.__name__

    def clone(self):
        return GeomapfishLocatorFilter()

    def displayName(self) -> str:
        name = self.settings.value("filter_name")
        if name != '':
            return name
        return self.tr('Geomapfish service')

    def prefix(self) -> str:
        return 'gmf'

    def hasConfigWidget(self) -> bool:
        return True

    def openConfigWidget(self, parent=None):
        ConfigDialog(parent).exec_()

    def create_transform(self):
        srv_crs_authid = self.settings.value('geomapfish_crs')
        src_crs = QgsCoordinateReferenceSystem(srv_crs_authid)
        assert src_crs.isValid()
        dst_crs = self.map_canvas.mapSettings().destinationCrs()
        self.transform = QgsCoordinateTransform(src_crs, dst_crs,
                                                QgsProject.instance())

    @staticmethod
    def url_with_param(url, params) -> str:
        url = QUrl(url)
        q = QUrlQuery(url)
        for key, value in params.items():
            q.addQueryItem(key, value)
        url.setQuery(q)
        return url.url()

    def emit_bad_configuration(self, err=None):
        result = QgsLocatorResult()
        result.filter = self
        result.displayString = self.tr('Locator filter is not configured.')
        result.description = err if err else self.tr(
            'Double-click to configure.')
        result.userData = FilterNotConfigured
        result.icon = QgsApplication.getThemeIcon('mIconWarning.svg')
        self.resultFetched.emit(result)
        return

    @pyqtSlot()
    def clear_results(self):
        if self.rubber_band:
            self.rubber_band.reset(QgsWkbTypes.PointGeometry)
        if self.current_timer is not None:
            self.current_timer.timeout.disconnect(self.clear_results)
            self.current_timer.stop()
            self.current_timer.deleteLater()
            self.current_timer = None

    def fetchResults(self, search, context, feedback):
        try:
            self.dbg_info("start GMF locator search...")

            url = self.settings.value('geomapfish_url')

            if url == "":
                self.emit_bad_configuration()
                return

            params = {
                'query': search,
                'limit': str(self.settings.value('total_limit')),
                'partitionlimit': str(self.settings.value('category_limit'))
            }

            if len(search) < 2:
                return

            headers = {b'User-Agent': self.USER_AGENT}
            if self.settings.value('geomapfish_user') != '':
                user = self.settings.value('geomapfish_user')
                password = self.settings.value('geomapfish_pass')
                auth_data = "{}:{}".format(user, password)
                b64 = QByteArray(auth_data.encode()).toBase64()
                headers[QByteArray('Authorization'.encode())] = QByteArray(
                    'Basic '.encode()) + b64

            url = self.url_with_param(url, params)
            self.dbg_info(url)

            nam = NetworkAccessManager()
            feedback.canceled.connect(nam.abort)
            (response, content) = nam.request(url,
                                              headers=headers,
                                              blocking=True)
            self.handle_response(response, content)

        except RequestsExceptionUserAbort:
            pass
        except RequestsException as err:
            self.emit_bad_configuration(str(err))
            self.info(err)
        except Exception as e:
            self.info(str(e), Qgis.Critical)
            #exc_type, exc_obj, exc_traceback = sys.exc_info()
            #filename = os.path.split(exc_traceback.tb_frame.f_code.co_filename)[1]
            #self.info('{} {} {}'.format(exc_type, filename, exc_traceback.tb_lineno), Qgis.Critical)
            #self.info(traceback.print_exception(exc_type, exc_obj, exc_traceback), Qgis.Critical)

        finally:
            self.finished.emit()

    def handle_response(self, response, content):
        try:
            if response.status_code != 200:
                self.info("Error with status code: {}".format(
                    response.status_code))
                return

            data = json.loads(content.decode('utf-8'))
            #self.dbg_info(data)

            features = data['features']
            for f in features:
                json_geom = json.dumps(f['geometry'])
                ogr_geom = ogr.CreateGeometryFromJson(json_geom)
                wkt = ogr_geom.ExportToWkt()
                geometry = QgsGeometry.fromWkt(wkt)
                self.dbg_info('---------')
                self.dbg_info(
                    QgsWkbTypes.geometryDisplayString(geometry.type()))
                self.dbg_info(f.keys())
                self.dbg_info('{} {}'.format(f['properties']['layer_name'],
                                             f['properties']['label']))
                self.dbg_info(f['bbox'])
                self.dbg_info(f['geometry'])
                if geometry is None:
                    continue
                result = QgsLocatorResult()
                result.filter = self
                result.displayString = f['properties']['label']
                if Qgis.QGIS_VERSION_INT >= 30100:
                    result.group = self.beautify_group(
                        f['properties']['layer_name'])
                result.userData = geometry
                self.resultFetched.emit(result)

        except Exception as e:
            self.info(str(e), Qgis.Critical)
            #exc_type, exc_obj, exc_traceback = sys.exc_info()
            #filename = os.path.split(exc_traceback.tb_frame.f_code.co_filename)[1]
            #self.info('{} {} {}'.format(exc_type, filename, exc_traceback.tb_lineno), Qgis.Critical)
            # self.info(traceback.print_exception(exc_type, exc_obj, exc_traceback), Qgis.Critical)

    def triggerResult(self, result):
        self.clear_results()
        if result.userData == FilterNotConfigured:
            self.openConfigWidget()
            if self.iface and hasattr(self.iface, 'invalidateLocatorResults'):
                # from QGIS 3.2 iface has invalidateLocatorResults
                self.iface.invalidateLocatorResults()
            return

        # this should be run in the main thread, i.e. mapCanvas should not be None
        geometry = result.userData
        geometry.transform(self.transform)

        self.rubber_band.reset(geometry.type())
        self.rubber_band.addGeometry(geometry, None)
        rect = geometry.boundingBox()
        rect.scale(1.5)
        self.map_canvas.setExtent(rect)
        self.map_canvas.refresh()

        self.current_timer = QTimer()
        self.current_timer.timeout.connect(self.clear_results)
        self.current_timer.setSingleShot(True)
        self.current_timer.start(5000)

    def beautify_group(self, group) -> str:
        if self.settings.value("remove_leading_digits"):
            group = re.sub('^[0-9]+', '', group)
        if self.settings.value("replace_underscore"):
            group = group.replace("_", " ")
        if self.settings.value("break_camelcase"):
            group = self.break_camelcase(group)
        return group

    def info(self, msg="", level=Qgis.Info):
        QgsMessageLog.logMessage('{} {}'.format(self.__class__.__name__, msg),
                                 'QgsLocatorFilter', level)

    def dbg_info(self, msg=""):
        if DEBUG:
            self.info(msg)

    @staticmethod
    def break_camelcase(identifier) -> str:
        matches = re.finditer(
            '.+?(?:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|$)',
            identifier)
        return ' '.join([m.group(0) for m in matches])
Example #21
0
class FinderBox(QComboBox):

    running = False
    toFinish = 0

    searchStarted = pyqtSignal()
    searchFinished = pyqtSignal()

    def __init__(self, finders, iface, parent=None):
        self.iface = iface
        self.mapCanvas = iface.mapCanvas()
        self.rubber = QgsRubberBand(self.mapCanvas)
        self.rubber.setColor(QColor(255, 255, 50, 200))
        self.rubber.setIcon(self.rubber.ICON_CIRCLE)
        self.rubber.setIconSize(15)
        self.rubber.setWidth(4)
        self.rubber.setBrushStyle(Qt.NoBrush)

        QComboBox.__init__(self, parent)
        self.setEditable(True)
        self.setInsertPolicy(QComboBox.InsertAtTop)
        self.setMinimumHeight(27)
        self.setSizePolicy(QSizePolicy.Expanding,
                           QSizePolicy.Fixed)

        self.insertSeparator(0)
        self.lineEdit().returnPressed.connect(self.search)

        self.resultView = QTreeView()
        self.resultView.setHeaderHidden(True)
        self.resultView.setMinimumHeight(300)
        self.resultView.activated.connect(self.itemActivated)
        self.resultView.pressed.connect(self.itemPressed)
        self.setView(self.resultView)

        self.resultModel = ResultModel(self)
        self.setModel(self.resultModel)

        self.finders = finders
        for finder in self.finders.values():
            finder.resultFound.connect(self.resultFound)
            finder.limitReached.connect(self.limitReached)
            finder.finished.connect(self.finished)
            finder.message.connect(self.message)

    def __del__(self):
        if self.rubber:
            self.iface.mapCanvas().scene().removeItem(self.rubber)
            del self.rubber

    def search(self):
        if self.running:
            return

        toFind = self.lineEdit().text()
        if not toFind or toFind == '':
            return

        self.running = True
        self.searchStarted.emit()

        self.resultModel.clearResults()
        self.resultModel.truncateHistory(MySettings().value("historyLength"))
        self.resultModel.setLoading(True)
        self.showPopup()

        QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)

        # create categories in special order and count activated ones
        for finder in self.finders.values():
            if finder.activated():
                self.resultModel.addResult(finder.name)

        bbox = self.mapCanvas.fullExtent()
        for finder in self.finders.values():
            if finder.activated():
                finder.start(toFind, bbox=bbox)

    def stop(self):
        for finder in self.finders.values():
            if finder.isRunning():
                finder.stop()

    def resultFound(self, finder, layername, value, geometry, srid):
        self.resultModel.addResult(finder.name, layername, value, geometry, srid)
        self.resultView.expandAll()

    def limitReached(self, finder, layername):
        self.resultModel.addEllipsys(finder.name, layername)

    def finished(self, finder):
        for finder in self.finders.values():
            if finder.isRunning():
                return

        self.running = False
        self.searchFinished.emit()

        self.resultModel.setLoading(False)

        QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)

    def message(self, finder, message, level):
        self.iface.messageBar().pushMessage("Quick Finder", message, level, 3)

    def itemActivated(self, index):
        item = self.resultModel.itemFromIndex(index)
        self.showItem(item)

    def itemPressed(self, index):
        item = self.resultModel.itemFromIndex(index)
        if QApplication.mouseButtons() == Qt.LeftButton:
            self.showItem(item)

    def showItem(self, item):
        if isinstance(item, ResultItem):
            self.resultModel.setSelected(item, self.resultView.palette())
            geometry = self.transformGeom(item)
            self.rubber.reset(geometry.type())
            self.rubber.setToGeometry(geometry, None)
            self.zoomToRubberBand()
            return

        if isinstance(item, GroupItem):
            child = item.child(0)
            if isinstance(child, ResultItem):
                self.resultModel.setSelected(item, self.resultView.palette())
                self.rubber.reset(child.geometry.type())
                for i in xrange(0, item.rowCount()):
                    geometry = self.transformGeom(item.child(i))
                    self.rubber.addGeometry(geometry, None)
                self.zoomToRubberBand()
            return

        if item.__class__.__name__ == 'QStandardItem':
            self.resultModel.setSelected(None, self.resultView.palette())
            self.rubber.reset()
            return

    def transformGeom(self, item):
        geometry = item.geometry
        src_crs = QgsCoordinateReferenceSystem()
        src_crs.createFromSrid(item.srid)
        dest_crs = self.mapCanvas.mapRenderer().destinationCrs()
        geom = item.geometry
        geom.transform( QgsCoordinateTransform(src_crs, dest_crs) )
        return geom

    def zoomToRubberBand(self):
        geom = self.rubber.asGeometry()
        if geom:
            rect = geom.boundingBox()
            rect.scale(1.5)
            self.mapCanvas.setExtent(rect)
            self.mapCanvas.refresh()
class wincan2qgep(QObject):

    name = u"&Wincan 2 QGEP"
    actions = None

    def __init__(self, iface):
        QObject.__init__(self)
        self.iface = iface
        self.actions = {}
        self.settings = MySettings()
        self.dlg = None

        # translation environment
        self.plugin_dir = os.path.dirname(__file__)
        locale = QSettings().value("locale/userLocale")[0:2]
        localePath = os.path.join(self.plugin_dir, 'i18n', 'wincan2qgep_{0}.qm'.format(locale))
        if os.path.exists(localePath):
            self.translator = QTranslator()
            self.translator.load(localePath)
            QCoreApplication.installTranslator(self.translator)

    def initGui(self):
        self.actions['openInspection'] = QAction(
            QIcon(":/plugins/wincan2qgep/icons/wincan_logo.png"),
            self.tr(u"Ouvrir une inspection"),
            self.iface.mainWindow())
        self.actions['openInspection'].triggered.connect(self.openInspection)
        self.iface.addPluginToMenu(self.name, self.actions['openInspection'])
        self.iface.addToolBarIcon(self.actions['openInspection'])

        self.actions['showSettings'] = QAction(
            QIcon(":/plugins/wincan2qgep/icons/settings.svg"),
            self.tr(u"&Settings"),
            self.iface.mainWindow())
        self.actions['showSettings'].triggered.connect(self.showSettings)
        self.iface.addPluginToMenu(self.name, self.actions['showSettings'])

        self.actions['help'] = QAction(
            QIcon(":/plugins/wincan2qgep/icons/help.svg"),
            self.tr("Help"),
            self.iface.mainWindow())
        self.actions['help'].triggered.connect(lambda: QDesktopServices().openUrl(QUrl("http://3nids.github.io/wincan2qgep")))
        self.iface.addPluginToMenu(self.name, self.actions['help'])

        self.rubber = QgsRubberBand(self.iface.mapCanvas())
        self.rubber.setColor(QColor(255, 255, 50, 200))
        self.rubber.setIcon(self.rubber.ICON_CIRCLE)
        self.rubber.setIconSize(15)
        self.rubber.setWidth(4)
        self.rubber.setBrushStyle(Qt.NoBrush)

    def unload(self):
        """ Unload plugin """
        for action in self.actions.itervalues():
            self.iface.removePluginMenu(self.name, action)
            self.iface.removeToolBarIcon(action)
        if self.rubber:
            self.iface.mapCanvas().scene().removeItem(self.rubber)
            del self.rubber
        if self.dlg:
            self.dlg.close()

    @pyqtSlot(str, QgsMessageBar.MessageLevel)
    def displayMessage(self, message, level):
        self.iface.messageBar().pushMessage("Wincan 2 QGEP", message, level)

    def showSettings(self):
        if ConfigurationDialog().exec_():
            self._reloadFinders()

    def openInspection(self):
        xmlPath = self.settings.value('xmlPath')
        if xmlPath == '':
            xmlPath = QgsProject.instance().homePath()
        filepath = QFileDialog.getOpenFileName(None, "Open WIncan inspection data", xmlPath, "Wincan file (*.xml)")
        #filepath = '/var/run/user/1000/gvfs/smb-share:server=s4laveyre.sige.ch,share=inspection_tv/SIGE_2014/Rapport 2014/SIGE 5004B 14/XML/Project.xml'

        if filepath:
            self.settings.set_value('xmlPath', os.path.dirname(os.path.realpath(filepath)))
            data = ImportData(filepath).data
            self.dlg = DataBrowserDialog(self.iface, data)
            self.dlg.show()
Example #23
0
class ConversionDialog(QDockWidget, FORM_CLASS):
    def __init__(self, iface, parent):
        super(ConversionDialog, self).__init__(parent)
        self.setupUi(self)
        self.iface = iface
        self.canvas = iface.mapCanvas()
        self.savedMapTool = None

        # Set up a polygon rubber band
        self.rubber = QgsRubberBand(self.canvas)
        self.rubber.setColor(QColor(255, 70, 0, 200))
        self.rubber.setWidth(3)
        self.rubber.setBrushStyle(Qt.NoBrush)

        self.tf = tzf_instance.getTZF()
        self.timeEdit.setDisplayFormat("HH:mm:ss")

        self.clipboard = QApplication.clipboard()
        font = self.sunTextEdit.font()
        font.setFamily("Courier New")
        self.sunTextEdit.setFont(font)

        # Set up a connection with the coordinate capture tool
        self.captureCoordinate = CaptureCoordinate(self.canvas)
        self.captureCoordinate.capturePoint.connect(self.capturedPoint)
        self.captureCoordinate.captureStopped.connect(self.stopCapture)
        self.coordCaptureButton.clicked.connect(self.startCapture)

        self.timezoneComboBox.view().setVerticalScrollBarPolicy(
            Qt.ScrollBarAsNeeded)
        self.timezoneComboBox.addItems(all_timezones)
        self.timezoneComboBox.currentIndexChanged.connect(
            self.timezone_changed)

        self.dateEdit.setCalendarPopup(True)
        self.coordCaptureButton.setIcon(
            QIcon(os.path.dirname(__file__) + "/images/coordCapture.svg"))
        self.currentDateTimeButton.setIcon(
            QIcon(os.path.dirname(__file__) + "/images/CurrentTime.png"))

        icon = QIcon(
            ':/images/themes/default/algorithms/mAlgorithmCheckGeometry.svg')
        self.epochCommitButton.setIcon(icon)
        self.epochmsCommitButton.setIcon(icon)
        self.julianCommitButton.setIcon(icon)
        self.iso8601CommitButton.setIcon(icon)
        self.iso8601_2_CommitButton.setIcon(icon)
        self.coordCommitButton.setIcon(icon)

        icon = QIcon(':/images/themes/default/mActionEditCopy.svg')
        self.epochCopyButton.setIcon(icon)
        self.epochmsCopyButton.setIcon(icon)
        self.julianCopyButton.setIcon(icon)
        self.iso8601CopyButton.setIcon(icon)
        self.iso8601_2_CopyButton.setIcon(icon)
        self.dateDifferenceCopyButton.setIcon(icon)
        self.coordCopyButton.setIcon(icon)
        self.timezoneCopyButton.setIcon(icon)
        self.sunCopyButton.setIcon(icon)
        self.sunAzimuthCopyButton.setIcon(icon)
        self.sunElevationCopyButton.setIcon(icon)

        self.coordLineEdit.returnPressed.connect(
            self.on_coordCommitButton_pressed)
        self.epochLineEdit.returnPressed.connect(
            self.on_epochCommitButton_pressed)
        self.epochmsLineEdit.returnPressed.connect(
            self.on_epochmsCommitButton_pressed)
        self.julienLineEdit.returnPressed.connect(
            self.on_julianCommitButton_pressed)
        self.iso8601LineEdit.returnPressed.connect(
            self.on_iso8601CommitButton_pressed)
        self.iso8601_2_LineEdit.returnPressed.connect(
            self.on_iso8601_2_CommitButton_pressed)

    def showEvent(self, e):
        self.initSystemTime()
        self.updateDateTime()

    def closeEvent(self, e):
        if self.savedMapTool:
            self.canvas.setMapTool(self.savedMapTool)
            self.savedMapTool = None
        self.rubber.reset()

    def initSystemTime(self):
        self.tz = tzlocal()
        dt = datetime.now(self.tz)
        try:
            name = dt.tzinfo.zone
        except Exception:
            name = dt.tzname()
        if name in win_tz_map:
            name = win_tz_map[name]
        id = self.timezoneComboBox.findText(name, Qt.MatchExactly)
        if id == -1:
            offset = int(dt.tzinfo.utcoffset(dt).total_seconds() / 3600.0)
            name = 'Etc/GMT{:+d}'.format(-offset)
        self.dt_utc = datetime.now(pytz.utc)
        self.tz = timezone(name)

    def getLocalDateTime(self):
        # print('getLocalDateTime')
        dt = self.dt_utc.astimezone(self.tz)
        return (dt)

    def startCapture(self):
        # print('startCapture')
        if self.coordCaptureButton.isChecked():
            self.savedMapTool = self.canvas.mapTool()
            self.canvas.setMapTool(self.captureCoordinate)
        else:
            if self.savedMapTool:
                self.canvas.setMapTool(self.savedMapTool)
                self.savedMapTool = None

    @pyqtSlot(QgsPointXY)
    def capturedPoint(self, pt):
        # print('capturedPoint')
        if self.isVisible() and self.coordCaptureButton.isChecked():
            coord = '{}, {}'.format(pt.y(), pt.x())
            self.coordLineEdit.setText(coord)
            if self.setCoordinateTimezone(pt.y(), pt.x()):
                self.updateDateTime()

    @pyqtSlot()
    def stopCapture(self):
        # print('stopCapture')
        self.coordCaptureButton.setChecked(False)
        self.rubber.reset()

    def setCoordinateTimezone(self, lat, lon):
        tzname = self.tf.timezone_at(lng=lon, lat=lat)
        if tzname != None:
            polygon = self.tf.get_geometry(tz_name=tzname)
            qgs_poly = tzf_to_qgis_polygon(polygon)
            canvas_crs = self.canvas.mapSettings().destinationCrs()
            if epsg4326 != canvas_crs:
                to_canvas_crs = QgsCoordinateTransform(epsg4326, canvas_crs,
                                                       QgsProject.instance())
                qgs_poly.transform(to_canvas_crs)
            self.rubber.reset()
            self.rubber.addGeometry(qgs_poly, None)
            self.rubber.show()

        # print('tzname {}'.format(tzname))
        if tzname:
            self.tz = timezone(tzname)
            dt = self.getLocalDateTime()
            dt = dt.replace(tzinfo=None)
            dt = self.tz.localize(dt)
            self.dt_utc = dt.astimezone(pytz.utc)
            return (True)
        return (False)

    def updateDateTime(self, id=Update.ALL):
        # print('updateDateTime')
        if self.hourModeCheckBox.isChecked():
            self.timeEdit.setDisplayFormat("HH:mm:ss")
        else:
            self.timeEdit.setDisplayFormat("hh:mm:ss AP")
        dt = self.getLocalDateTime()
        # offset = int(dt.tzinfo.utcoffset(dt).total_seconds()/3600.0)
        offset = dt.strftime('%z')
        if id != Update.DATE:
            self.dateEdit.blockSignals(True)
            self.dateEdit.setDate(QDate(dt.year, dt.month, dt.day))
            self.dateEdit.blockSignals(False)
        if id != Update.TIME:
            self.timeEdit.blockSignals(True)
            self.timeEdit.setTime(
                QTime(dt.hour, dt.minute, dt.second,
                      int(dt.microsecond / 1000)))
            self.timeEdit.blockSignals(False)
        if id != Update.EPOCH:
            try:
                sec = int(
                    (self.dt_utc -
                     datetime.fromtimestamp(0, pytz.utc)).total_seconds())
            except Exception:
                sec = 'Undefined'
            self.epochLineEdit.setText('{}'.format(sec))
        if id != Update.EPOCHMS:
            try:
                msec = int(
                    (self.dt_utc -
                     datetime.fromtimestamp(0, pytz.utc)).total_seconds() *
                    1000.0)
            except Exception:
                msec = 'Undefined'
            self.epochmsLineEdit.setText('{}'.format(msec))
        if id != Update.TIME_ZONE:
            # This should always be true because we have forced the timezone to adhear to the list
            name = dt.tzinfo.zone
            id = self.timezoneComboBox.findText(name, Qt.MatchExactly)
            self.timezoneComboBox.blockSignals(True)
            self.timezoneComboBox.setCurrentIndex(id)
            self.timezoneComboBox.blockSignals(False)
        if id != Update.JULIAN:
            (val1, val2) = gcal2jd(dt.year, dt.month, dt.day)
            self.julienLineEdit.setText('{}'.format(val1 + val2))

        if id != Update.UTC:
            if dt.microsecond == 0:
                self.iso8601LineEdit.setText(
                    self.dt_utc.strftime('%Y-%m-%dT%H:%M:%SZ'))
            else:
                self.iso8601LineEdit.setText(
                    self.dt_utc.strftime('%Y-%m-%dT%H:%M:%S.%fZ'))

        tinfo = dt.timetuple()
        self.dayOfWeekLineEdit.setText(DOW[tinfo.tm_wday])
        self.doyLineEdit.setText('{}'.format(tinfo.tm_yday))

        # self.timezoneOffsetLineEdit.setText('{:+d}'.format(offset))
        self.timezoneOffsetLineEdit.setText(offset)

        self.updateTimeDelta()
        self.updateSunMoon()
        self.updateReverseGeo()

    def updateSunMoon(self):
        # print('updateSunMoon')
        try:
            (lat, lon) = parseDMSString(self.coordLineEdit.text().strip())
        except Exception:
            self.clearSun()
            return
        locl = astral.LocationInfo('', '', '', lat, lon)
        dt = self.getLocalDateTime()
        try:
            if self.displaySunUtcCheckBox.isChecked():
                s = sun(locl.observer, date=dt, tzinfo=pytz.utc)
            else:
                s = sun(locl.observer, date=dt, tzinfo=dt.tzinfo)
        except Exception:
            self.clearSun()
            return
        msg = []
        if self.displaySunUtcCheckBox.isChecked():
            fmt = '%Y-%m-%dT%H:%M:%SZ'
        else:
            fmt = '%Y-%m-%dT%H:%M:%S%z'
        for k, v in s.items():
            msg.append('{:>7}, {}'.format(k, v.strftime(fmt)))
        sunstr = '\n'.join(msg)
        self.sunTextEdit.clear()
        self.sunTextEdit.insertPlainText(sunstr)

        azimuth = astral.sun.azimuth(locl.observer, dt)
        elev = astral.sun.elevation(locl.observer, dt)

        self.sunAzimuthLineEdit.setText('{:.8f}'.format(azimuth))
        self.sunElevationLineEdit.setText('{:.8f}'.format(elev))

    def updateReverseGeo(self):
        try:
            (lat, lon) = parseDMSString(self.coordLineEdit.text().strip())
        except Exception:
            self.clearReverseGeo()
            return
        results = rg.search(((lat, lon)), mode=1)

        self.clearReverseGeo()
        str = '{}, {}, {}'.format(results[0]['cc'], results[0]['admin1'],
                                  results[0]['admin2'])
        self.reverseGeoLinedit.setText(str)

        # Get the azimuth and distance to the nearest know location
        l = geod.Inverse(lat, lon, float(results[0]['lat']),
                         float(results[0]['lon']))
        heading = l['azi1']
        dist = l['s12'] / 1000.0  # put it in kilometers
        str = '{} {:.1f}° / {:.1f} km'.format(results[0]['name'], heading,
                                              dist)
        self.headingDistLineEdit.setText(str)

    def clearReverseGeo(self):
        self.reverseGeoLinedit.setText('')
        self.headingDistLineEdit.setText('')

    def clearSun(self):
        self.sunTextEdit.clear()
        self.sunAzimuthLineEdit.setText('')
        self.sunElevationLineEdit.setText('')

    def timezone_changed(self, index):
        tz_name = str(self.timezoneComboBox.itemText(index))
        self.tz = timezone(tz_name)
        dt = self.getLocalDateTime()
        dt = dt.replace(tzinfo=None)
        dt = self.tz.localize(dt)
        self.dt_utc = dt.astimezone(pytz.utc)
        self.updateDateTime(Update.TIME_ZONE)

    def on_currentDateTimeButton_pressed(self):
        self.initSystemTime()
        self.coordLineEdit.setText('')
        self.updateDateTime()

    def on_hourModeCheckBox_stateChanged(self, state):
        if self.hourModeCheckBox.isChecked():
            self.timeEdit.setDisplayFormat("HH:mm:ss")
        else:
            self.timeEdit.setDisplayFormat("hh:mm:ss AP")

    def on_displaySunUtcCheckBox_stateChanged(self, state):
        self.updateSunMoon()

    def on_dateEdit_dateChanged(self, date):
        olddt = self.getLocalDateTime()
        dt = datetime(date.year(), date.month(), date.day(), olddt.hour,
                      olddt.minute, olddt.second, olddt.microsecond)
        dt = self.tz.localize(dt)
        self.dt_utc = dt.astimezone(pytz.utc)
        self.updateDateTime(Update.DATE)

    def on_timeEdit_timeChanged(self, time):
        # print('on_timeEdit_timeChanged')
        olddt = self.getLocalDateTime()
        dt = datetime(olddt.year, olddt.month, olddt.day, time.hour(),
                      time.minute(), time.second(),
                      time.msec() * 1000)
        dt = self.tz.localize(dt)
        self.dt_utc = dt.astimezone(pytz.utc)
        self.updateDateTime(Update.TIME)

    def on_coordCommitButton_pressed(self):
        # print('coordCommitButton Pressed')
        try:
            coord = self.coordLineEdit.text().strip()
            if not coord:
                self.clearSun()
                self.clearReverseGeo()
                return
            (lat, lon) = parseDMSString(coord)
            self.setCoordinateTimezone(lat, lon)
            self.updateDateTime()
        except Exception:
            self.iface.messageBar().pushMessage(
                "",
                "Invalid 'latitude, longitude'",
                level=Qgis.Warning,
                duration=2)
            return

    def on_epochCommitButton_pressed(self):
        # print('epochCommitButton Pressed')
        try:
            epoch = long(self.epochLineEdit.text().strip())
        except Exception:
            return
        self.dt_utc = datetime.fromtimestamp(epoch, pytz.utc)
        self.updateDateTime(Update.EPOCH)

    def on_epochmsCommitButton_pressed(self):
        # print('epochmsCommitButton Pressed')
        try:
            epoch = long(self.epochmsLineEdit.text().strip()) / 1000.0
        except Exception:
            return
        self.dt_utc = datetime.fromtimestamp(epoch, pytz.utc)
        self.updateDateTime(Update.EPOCHMS)

    def on_julianCommitButton_pressed(self):
        # print('on_julianCommitButton_pressed Pressed')
        try:
            julian = float(self.julienLineEdit.text().strip()) - MJD_0
            date = jd2gcal(MJD_0, julian)
        except Exception:
            self.iface.messageBar().pushMessage("",
                                                "Invalid julian date",
                                                level=Qgis.Warning,
                                                duration=2)
            return
        olddt = self.getLocalDateTime()
        dt = datetime(date[0], date[1], date[2], olddt.hour, olddt.minute,
                      olddt.second, olddt.microsecond)
        dt = self.tz.localize(dt)
        self.dt_utc = dt.astimezone(pytz.utc)
        self.updateDateTime(Update.JULIAN)

    def on_iso8601CommitButton_pressed(self):
        str = self.iso8601LineEdit.text().strip()
        if not str:
            return
        try:
            dt = dateutil.parser.parse(str,
                                       default=datetime(MINYEAR,
                                                        1,
                                                        1,
                                                        hour=0,
                                                        minute=0,
                                                        second=0,
                                                        microsecond=0,
                                                        tzinfo=pytz.utc))
        except Exception:
            self.iface.messageBar().pushMessage(
                "",
                "Invalid ISO8601 date and time",
                level=Qgis.Warning,
                duration=2)
            return
        self.dt_utc = dt.astimezone(pytz.utc)
        self.updateDateTime(Update.UTC)

    def on_iso8601_2_CommitButton_pressed(self):
        self.updateTimeDelta()

    def updateTimeDelta(self):
        str = self.iso8601_2_LineEdit.text().strip()
        if not str:
            return
        try:
            dt_delta = dateutil.parser.parse(str,
                                             default=datetime(MINYEAR,
                                                              1,
                                                              1,
                                                              hour=0,
                                                              minute=0,
                                                              second=0,
                                                              microsecond=0,
                                                              tzinfo=pytz.utc))
        except Exception:
            self.iface.messageBar().pushMessage(
                "",
                "Invalid ISO8601 date and time",
                level=Qgis.Warning,
                duration=2)
            return
        diff = relativedelta(dt_delta, self.dt_utc)
        msg = '{}y {}m {}d {}h {}m {}s {}uS'.format(diff.years, diff.months,
                                                    diff.days, diff.hours,
                                                    diff.minutes, diff.seconds,
                                                    diff.microseconds)
        self.dtDeltaLineEdit.setText(msg)

    def on_timezoneCopyButton_pressed(self):
        s = str(self.timezoneComboBox.currentText())
        self.clipboard.setText(s)
        self.iface.messageBar().pushMessage(
            "",
            "{} copied to the clipboard".format(s),
            level=Qgis.Info,
            duration=3)

    def on_coordCopyButton_pressed(self):
        s = self.coordLineEdit.text().strip()
        self.clipboard.setText(s)
        self.iface.messageBar().pushMessage(
            "",
            "{} copied to the clipboard".format(s),
            level=Qgis.Info,
            duration=3)

    def on_epochCopyButton_pressed(self):
        s = self.epochLineEdit.text().strip()
        self.clipboard.setText(s)
        self.iface.messageBar().pushMessage(
            "",
            "{} copied to the clipboard".format(s),
            level=Qgis.Info,
            duration=3)

    def on_epochmsCopyButton_pressed(self):
        s = self.epochmsLineEdit.text().strip()
        self.clipboard.setText(s)
        self.iface.messageBar().pushMessage(
            "",
            "{} copied to the clipboard".format(s),
            level=Qgis.Info,
            duration=3)

    def on_julianCopyButton_pressed(self):
        s = self.julienLineEdit.text().strip()
        self.clipboard.setText(s)
        self.iface.messageBar().pushMessage(
            "",
            "{} copied to the clipboard".format(s),
            level=Qgis.Info,
            duration=3)

    def on_iso8601CopyButton_pressed(self):
        s = self.iso8601LineEdit.text().strip()
        self.clipboard.setText(s)
        self.iface.messageBar().pushMessage(
            "",
            "{} copied to the clipboard".format(s),
            level=Qgis.Info,
            duration=3)

    def on_iso8601_2_CopyButton_pressed(self):
        s = self.iso8601_2_LineEdit.text().strip()
        self.clipboard.setText(s)
        self.iface.messageBar().pushMessage(
            "",
            "{} copied to the clipboard".format(s),
            level=Qgis.Info,
            duration=3)

    def on_dateDifferenceCopyButton_pressed(self):
        s = self.dtDeltaLineEdit.text().strip()
        self.clipboard.setText(s)
        self.iface.messageBar().pushMessage(
            "",
            "{} copied to the clipboard".format(s),
            level=Qgis.Info,
            duration=3)

    def on_sunCopyButton_pressed(self):
        s = self.sunTextEdit.toPlainText().strip()
        self.clipboard.setText(s)
        self.iface.messageBar().pushMessage(
            "",
            "Sun information copied to the clipboard",
            level=Qgis.Info,
            duration=3)

    def on_sunAzimuthCopyButton_pressed(self):
        s = self.sunAzimuthLineEdit.text().strip()
        self.clipboard.setText(s)
        self.iface.messageBar().pushMessage(
            "",
            "{} copied to the clipboard".format(s),
            level=Qgis.Info,
            duration=3)

    def on_sunElevationCopyButton_pressed(self):
        s = self.sunElevationLineEdit.text().strip()
        self.clipboard.setText(s)
        self.iface.messageBar().pushMessage(
            "",
            "{} copied to the clipboard".format(s),
            level=Qgis.Info,
            duration=3)
Example #24
0
class FinderBox(QComboBox):

    running = False
    toFinish = 0

    searchStarted = pyqtSignal()
    searchFinished = pyqtSignal()

    def __init__(self, finders, iface, parent=None):
        self.iface = iface
        self.mapCanvas = iface.mapCanvas()
        self.rubber = QgsRubberBand(self.mapCanvas)
        self.rubber.setColor(QColor(255, 255, 50, 200))
        self.rubber.setIcon(self.rubber.ICON_CIRCLE)
        self.rubber.setIconSize(15)
        self.rubber.setWidth(4)
        self.rubber.setBrushStyle(Qt.NoBrush)

        QComboBox.__init__(self, parent)
        self.setEditable(True)
        self.setInsertPolicy(QComboBox.InsertAtTop)
        self.setMinimumHeight(27)
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)

        self.insertSeparator(0)
        self.lineEdit().returnPressed.connect(self.search)

        self.resultView = QTreeView()
        self.resultView.setHeaderHidden(True)
        self.resultView.setMinimumHeight(300)
        self.resultView.activated.connect(self.itemActivated)
        self.resultView.pressed.connect(self.itemPressed)
        self.setView(self.resultView)

        self.resultModel = ResultModel(self)
        self.setModel(self.resultModel)

        self.finders = finders
        for finder in self.finders.values():
            finder.resultFound.connect(self.resultFound)
            finder.limitReached.connect(self.limitReached)
            finder.finished.connect(self.finished)

        self.clearButton = QPushButton(self)
        self.clearButton.setIcon(
            QIcon(":/plugins/quickfinder/icons/draft.svg"))
        self.clearButton.setText('')
        self.clearButton.setFlat(True)
        self.clearButton.setCursor(QCursor(Qt.ArrowCursor))
        self.clearButton.setStyleSheet('border: 0px; padding: 0px;')
        self.clearButton.clicked.connect(self.clear)

        layout = QHBoxLayout(self)
        self.setLayout(layout)
        layout.addStretch()
        layout.addWidget(self.clearButton)
        layout.addSpacing(20)

        buttonSize = self.clearButton.sizeHint()
        # frameWidth = self.lineEdit().style().pixelMetric(QtGui.QStyle.PM_DefaultFrameWidth)
        padding = buttonSize.width()  # + frameWidth + 1
        self.lineEdit().setStyleSheet('QLineEdit {padding-right: %dpx; }' %
                                      padding)

    def __del__(self):
        if self.rubber:
            self.iface.mapCanvas().scene().removeItem(self.rubber)
            del self.rubber

    def clearSelection(self):
        self.resultModel.setSelected(None, self.resultView.palette())
        self.rubber.reset()

    def clear(self):
        self.clearSelection()
        self.resultModel.clearResults()
        self.lineEdit().setText('')

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Escape:
            self.clearSelection()
        QComboBox.keyPressEvent(self, event)

    def search(self):
        if self.running:
            return

        toFind = self.lineEdit().text()
        if not toFind or toFind == '':
            return

        self.running = True
        self.searchStarted.emit()

        self.clearSelection()
        self.resultModel.clearResults()
        self.resultModel.truncateHistory(MySettings().value("historyLength"))
        self.resultModel.setLoading(True)
        self.showPopup()

        QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)

        self.findersToStart = []
        for finder in self.finders.values():
            if finder.activated():
                self.findersToStart.append(finder)

        bbox = self.mapCanvas.fullExtent()

        while len(self.findersToStart) > 0:
            finder = self.findersToStart[0]
            self.findersToStart.remove(finder)
            self.resultModel.addResult(finder.name)
            finder.start(toFind, bbox=bbox)

        # For case there is no finder activated
        self.finished(None)

    def stop(self):
        self.findersToStart = []
        for finder in self.finders.values():
            if finder.isRunning():
                finder.stop()
        self.finished(None)

    def resultFound(self, finder, layername, value, geometry, srid):
        self.resultModel.addResult(finder.name, layername, value, geometry,
                                   srid)
        self.resultView.expandAll()

    def limitReached(self, finder, layername):
        self.resultModel.addEllipsys(finder.name, layername)

    def finished(self, finder):
        if len(self.findersToStart) > 0:
            return
        for finder in self.finders.values():
            if finder.isRunning():
                return

        self.running = False
        self.searchFinished.emit()

        self.resultModel.setLoading(False)

        QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)

    def itemActivated(self, index):
        item = self.resultModel.itemFromIndex(index)
        self.showItem(item)

    def itemPressed(self, index):
        item = self.resultModel.itemFromIndex(index)
        if QApplication.mouseButtons() == Qt.LeftButton:
            self.showItem(item)

    def showItem(self, item):
        if isinstance(item, ResultItem):
            self.resultModel.setSelected(item, self.resultView.palette())
            geometry = self.transformGeom(item)
            self.rubber.reset(geometry.type())
            self.rubber.setToGeometry(geometry, None)
            self.zoomToRubberBand()
            return

        if isinstance(item, GroupItem):
            child = item.child(0)
            if isinstance(child, ResultItem):
                self.resultModel.setSelected(item, self.resultView.palette())
                self.rubber.reset(child.geometry.type())
                for i in xrange(0, item.rowCount()):
                    geometry = self.transformGeom(item.child(i))
                    self.rubber.addGeometry(geometry, None)
                self.zoomToRubberBand()
            return

        if item.__class__.__name__ == 'QStandardItem':
            self.clearSelection()

    def transformGeom(self, item):
        src_crs = QgsCoordinateReferenceSystem()
        src_crs.createFromSrid(item.srid)
        dest_crs = self.mapCanvas.mapRenderer().destinationCrs()
        geom = QgsGeometry(item.geometry)
        geom.transform(QgsCoordinateTransform(src_crs, dest_crs))
        return geom

    def zoomToRubberBand(self):
        geom = self.rubber.asGeometry()
        if geom:
            rect = geom.boundingBox()
            rect.scale(1.5)
            self.mapCanvas.setExtent(rect)
            self.mapCanvas.refresh()
Example #25
0
class teamqgisDock(QDockWidget, Ui_teamqgis):
    dockRemoved = pyqtSignal(str)

    def __init__(self, iface, layer, currentFeature):
        self.iface = iface
        self.layer = layer
        self.proj = QgsProject.instance()
        self.renderer = self.iface.mapCanvas().mapRenderer()
        self.settings = MySettings()
        QDockWidget.__init__(self)
        self.setupUi(self)

        # Track attr warnings so they are not repeated for multiple items
        self.warned_attr_values = []

        self.setWindowTitle("teamqgis: %s" % layer.name())
        if layer.hasGeometryType() is False:
            self.panCheck.setChecked(False)
            self.panCheck.setEnabled(False)
            self.scaleCheck.setChecked(False)
            self.scaleCheck.setEnabled(False)

        self.previousButton.setArrowType(Qt.LeftArrow)
        self.nextButton.setArrowType(Qt.RightArrow)
        icon = QIcon(":/plugins/teamqgis/icons/openform.svg")
        self.editFormButton.setIcon(icon)

        # actions
        icon = QIcon(":/plugins/teamqgis/icons/action.svg")
        self.actionButton.setIcon(icon)
        self.attrAction = layer.actions()
        actions = [self.attrAction[i] for i in range(self.attrAction.size())]
        preferredAction = layer.customProperty("teamqgisPreferedAction", "")
        if preferredAction not in actions:
            dfltAction = self.attrAction.defaultAction()
            if dfltAction > len(actions):
                preferredAction = self.attrAction[dfltAction].name()
        preferredActionFound = False
        for i, action in enumerate(actions):
            qAction = QAction(QIcon(":/plugins/teamqgis/icons/action.svg"),
                              action.name(), self)
            qAction.triggered.connect(lambda: self.doAction(i))
            self.actionButton.addAction(qAction)
            if action.name() == preferredAction:
                self.actionButton.setDefaultAction(qAction)
                preferredActionFound = True
        if len(actions) == 0:
            self.actionButton.setEnabled(False)
        elif not preferredActionFound:
            self.actionButton.setDefaultAction(self.actionButton.actions()[0])

        self.nameComboBoxes = [
            self.fieldOneNameComboBox, self.fieldTwoNameComboBox,
            self.fieldThreeNameComboBox
        ]
        self.valueComboBoxes = [
            self.fieldOneValueComboBox, self.fieldTwoValueComboBox,
            self.fieldThreeValueComboBox
        ]

        self.updateNameComboBoxes()

        # Restore saved nameComboBox current indices if they exist
        for nameComboBox in self.nameComboBoxes:
            fieldName = self.layer.customProperty("teamqgis" +
                                                  nameComboBox.objectName())
            if fieldName != None:
                nameComboBox.setCurrentIndex(nameComboBox.findText(fieldName))

        self.rubber = QgsRubberBand(self.iface.mapCanvas())
        self.selectionChanged()
        if currentFeature == self.listCombo.currentIndex():
            self.on_listCombo_currentIndexChanged(currentFeature)
        else:
            self.listCombo.setCurrentIndex(currentFeature)
        self.layer.layerDeleted.connect(self.close)
        self.layer.selectionChanged.connect(self.selectionChanged)
        self.layer.layerModified.connect(self.layerChanged)
        self.layer.editingStopped.connect(self.editingStopped)
        self.layer.editingStarted.connect(self.editingStarted)

        QObject.connect(self.proj, SIGNAL("allowedClassesChanged()"),
                        self.updateValueComboBoxes)

    def updateNameComboBoxes(self):
        fieldNameMap = self.layer.dataProvider().fieldNameMap()
        allFields = fieldNameMap.keys()
        if 'ID' in allFields: allFields.remove('ID')
        if 'FID' in allFields: allFields.remove('FID')
        for nameComboBox in self.nameComboBoxes:
            nameComboBox.clear()
            nameComboBox.addItems(allFields)

    def updateValueComboBoxes(self):
        feature = self.getCurrentItem()
        for (valueComboBox, nameComboBox) in zip(self.valueComboBoxes,
                                                 self.nameComboBoxes):
            valueComboBox.clear()
            allowedClasses, hasAllowedClasses = self.proj.readListEntry(
                "teamqgis", "allowedClasses")
            if hasAllowedClasses:
                valueComboBox.addItems(allowedClasses)
            attr_value = str(feature[nameComboBox.currentText()])
            if (allowedClasses
                    == None) or (attr_value not in allowedClasses) and (
                        attr_value not in self.warned_attr_values):
                self.iface.messageBar().pushMessage(
                    "Class name not in allowed class list",
                    'Assign an allowed class or add "%s" to allowed class list'
                    % attr_value,
                    level=QgsMessageBar.WARNING,
                    duration=3)
                self.warned_attr_values.append(attr_value)
                valueComboBox.addItem(attr_value)
            valueComboBox.setCurrentIndex(valueComboBox.findText(attr_value))

    def setRubber(self, feature):
        self.rubber.setColor(self.settings.value("rubberColor"))
        self.rubber.setWidth(self.settings.value("rubberWidth"))
        ##self.rubber.setLineStyle(Qt.DotLine)
        self.rubber.setBrushStyle(Qt.NoBrush)
        self.rubber.setToGeometry(feature.geometry(), self.layer)

    def closeEvent(self, e):
        self.rubber.reset()
        self.layer.layerDeleted.disconnect(self.close)
        self.layer.selectionChanged.disconnect(self.selectionChanged)
        self.layer.layerModified.disconnect(self.layerChanged)
        self.layer.editingStopped.disconnect(self.editingStopped)
        self.layer.editingStarted.disconnect(self.editingStarted)
        if self.settings.value("saveSelectionInProject"):
            self.layer.setCustomProperty("teamqgisSelection", repr([]))
        self.dockRemoved.emit(self.layer.id())

    def selectionChanged(self):
        self.cleanBrowserFields()
        self.rubber.reset()
        nItems = self.layer.selectedFeatureCount()
        if nItems < 1:
            self.close()
            self.layer.emit(SIGNAL("browserNoItem()"))
            return
        self.browseFrame.setEnabled(True)
        self.subset = self.layer.selectedFeaturesIds()
        if self.settings.value("saveSelectionInProject"):
            self.layer.setCustomProperty("teamqgisSelection",
                                         repr(self.subset))
        for fid in self.subset:
            self.listCombo.addItem("%u" % fid)
        self.setRubber(self.getCurrentItem())
        self.updateValueComboBoxes()

    def layerChanged(self):
        self.applyChangesButton.setEnabled(True)

    def editingStarted(self):
        for valueComboBox in self.valueComboBoxes:
            valueComboBox.setEnabled(True)
        self.translateRightButton.setEnabled(True)
        self.translateLeftButton.setEnabled(True)
        self.translateUpButton.setEnabled(True)
        self.translateDownButton.setEnabled(True)
        self.editFormButton.setDown(True)

    def editingStopped(self):
        self.applyChangesButton.setEnabled(False)
        for valueComboBox in self.valueComboBoxes:
            valueComboBox.setEnabled(False)
        self.translateRightButton.setEnabled(False)
        self.translateLeftButton.setEnabled(False)
        self.translateUpButton.setEnabled(False)
        self.translateDownButton.setEnabled(False)
        self.editFormButton.setDown(False)

    def cleanBrowserFields(self):
        self.currentPosLabel.setText('0/0')
        self.listCombo.clear()

    def panScaleToItem(self, feature):
        if self.panCheck.isChecked() is False:
            return
        featBobo = feature.geometry().boundingBox()
        # if scaling and bobo has width and height (i.e. not a point)
        if self.scaleCheck.isChecked(
        ) and featBobo.width() != 0 and featBobo.height() != 0:
            featBobo.scale(self.settings.value("scale"))
            ul = self.renderer.layerToMapCoordinates(
                self.layer, QgsPoint(featBobo.xMinimum(), featBobo.yMaximum()))
            ur = self.renderer.layerToMapCoordinates(
                self.layer, QgsPoint(featBobo.xMaximum(), featBobo.yMaximum()))
            ll = self.renderer.layerToMapCoordinates(
                self.layer, QgsPoint(featBobo.xMinimum(), featBobo.yMinimum()))
            lr = self.renderer.layerToMapCoordinates(
                self.layer, QgsPoint(featBobo.xMaximum(), featBobo.yMinimum()))
            x = (ul.x(), ur.x(), ll.x(), lr.x())
            y = (ul.y(), ur.y(), ll.y(), lr.y())
            x0 = min(x)
            y0 = min(y)
            x1 = max(x)
            y1 = max(y)
        else:
            panTo = self.renderer.layerToMapCoordinates(
                self.layer, featBobo.center())
            mapBobo = self.iface.mapCanvas().extent()
            xshift = panTo.x() - mapBobo.center().x()
            yshift = panTo.y() - mapBobo.center().y()
            x0 = mapBobo.xMinimum() + xshift
            y0 = mapBobo.yMinimum() + yshift
            x1 = mapBobo.xMaximum() + xshift
            y1 = mapBobo.yMaximum() + yshift
        self.iface.mapCanvas().setExtent(QgsRectangle(x0, y0, x1, y1))
        self.iface.mapCanvas().refresh()

    def getCurrentItem(self):
        i = self.listCombo.currentIndex()
        if i == -1:
            return None
        f = QgsFeature()
        if self.layer.getFeatures(QgsFeatureRequest().setFilterFid(
                self.subset[i])).nextFeature(f):
            return f
        else:
            raise NameError("feature not found")

    def doAction(self, i):
        f = self.getCurrentItem()
        self.actionButton.setDefaultAction(self.actionButton.actions()[i])
        self.layer.setCustomProperty("teamqgisPreferedAction",
                                     self.attrAction[i].name())
        self.attrAction.doActionFeature(i, f)

    def doTranslate(self, trans):
        # Based on the "doaffine" function in the qgsAffine plugin
        if (self.layer.geometryType() == 2):
            start = 1
        else:
            start = 0
        if (not self.layer.isEditable()):
            self.iface.messageBar().pushMessage(
                "Layer not in edit mode",
                'Select a vector layer and choose "Toggle Editing"',
                level=QgsMessageBar.WARNING)
        else:
            feature = self.getCurrentItem()
            result = feature.geometry()
            i = start
            vertex = result.vertexAt(i)
            fid = feature.id()
            while (vertex != QgsPoint(0, 0)):
                newx = vertex.x() + trans[0] * float(
                    self.settings.value("xres"))
                newy = vertex.y() + trans[1] * float(
                    self.settings.value("yres"))
                result.moveVertex(newx, newy, i)
                i += 1
                vertex = result.vertexAt(i)
            self.layer.changeGeometry(fid, result)
            self.iface.mapCanvas().refresh()
            self.rubber.reset()
            self.setRubber(feature)

    def changeAttribute(self, i, fieldNameComboBox, fieldValueComboBox):
        fieldValueComboBox.setCurrentIndex(i)
        feature = self.getCurrentItem()
        attr_index = self.layer.dataProvider().fieldNameMap()[
            fieldNameComboBox.currentText()]
        self.layer.changeAttributeValue(feature.id(), attr_index,
                                        fieldValueComboBox.currentText())
        self.iface.mapCanvas().refresh()
        self.updateValueComboBoxes()

    def nameComboBox_activated(self, i, nameComboBox):
        self.layer.setCustomProperty('teamqgis' + nameComboBox.objectName(),
                                     nameComboBox.currentText())

    @pyqtSlot(name="on_previousButton_clicked")
    def previousFeature(self):
        i = self.listCombo.currentIndex()
        n = max(0, i - 1)
        self.listCombo.setCurrentIndex(n)
        self.saveCurrentFeature(n)

    @pyqtSlot(name="on_nextButton_clicked")
    def nextFeature(self):
        self.warned_attr_values = []  # Reset attr warnings
        i = self.listCombo.currentIndex()
        c = self.listCombo.count()
        n = min(i + 1, c - 1)
        self.listCombo.setCurrentIndex(n)
        self.saveCurrentFeature(n)

    @pyqtSlot(int, name="on_listCombo_activated")
    def saveCurrentFeature(self, i):
        if self.settings.value("saveSelectionInProject"):
            self.layer.setCustomProperty("teamqgisCurrentItem", i)

    @pyqtSlot(int, name="on_fieldOneNameComboBox_activated")
    def fieldOneNameComboBox_activated(self, i):
        self.nameComboBox_activated(i, self.fieldOneNameComboBox)

    @pyqtSlot(int, name="on_fieldTwoNameComboBox_activated")
    def fieldTwoNameComboBox_activated(self, i):
        self.nameComboBox_activated(i, self.fieldTwoNameComboBox)

    @pyqtSlot(int, name="on_fieldThreeNameComboBox_activated")
    def fieldThreeNameComboBox_activated(self, i):
        self.nameComboBox_activated(i, self.fieldThreeNameComboBox)

    @pyqtSlot(int, name="on_listCombo_currentIndexChanged")
    def on_listCombo_currentIndexChanged(self, i):
        feature = self.getCurrentItem()
        if feature is None:
            return
        self.rubber.reset()
        if self.listCombo.count() > 1:
            self.setRubber(feature)
            self.updateValueComboBoxes()
        # scale to feature
        self.panScaleToItem(feature)
        # Update browser
        self.currentPosLabel.setText("%u/%u" % (i + 1, len(self.subset)))
        # emit signal
        self.layer.emit(SIGNAL("browserCurrentItem(long)"), feature.id())

    @pyqtSlot(int, name="on_panCheck_stateChanged")
    def on_panCheck_stateChanged(self, i):
        if self.panCheck.isChecked():
            self.scaleCheck.setEnabled(True)
            feature = self.getCurrentItem()
            if feature is None:
                return
            self.panScaleToItem(feature)
        else:
            self.scaleCheck.setEnabled(False)

    @pyqtSlot(int, name="on_scaleCheck_stateChanged")
    def on_scaleCheck_stateChanged(self, i):
        if self.scaleCheck.isChecked():
            feature = self.getCurrentItem()
            if feature is None:
                return
            self.panScaleToItem(feature)

    @pyqtSlot(name="on_editFormButton_clicked")
    def openFeatureForm(self):
        if (self.layer.isEditable()):
            self.layer.commitChanges()
        else:
            self.layer.startEditing()

    @pyqtSlot(name="on_translateRightButton_clicked")
    def doTranslateRight(self):
        self.doTranslate((1, 0))

    @pyqtSlot(name="on_translateLeftButton_clicked")
    def doTranslateLeft(self):
        self.doTranslate((-1, 0))

    @pyqtSlot(name="on_translateUpButton_clicked")
    def doTranslateUp(self):
        self.doTranslate((0, 1))

    @pyqtSlot(name="on_translateDownButton_clicked")
    def doTranslateDown(self):
        self.doTranslate((0, -1))

    @pyqtSlot(name="on_applyChangesButton_clicked")
    def applyChanges(self):
        self.layer.commitChanges()
        self.layer.startEditing()
        self.layer.updateExtents()
        self.iface.mapCanvas().refresh()

    @pyqtSlot(int, name="on_fieldOneValueComboBox_activated")
    def on_fieldOneValueComboBox_activated(self, i):
        self.changeAttribute(i, self.fieldOneNameComboBox,
                             self.fieldOneValueComboBox)

    @pyqtSlot(int, name="on_fieldTwoValueComboBox_activated")
    def on_fieldTwoValueComboBox_activated(self, i):
        self.changeAttribute(i, self.fieldTwoNameComboBox,
                             self.fieldTwoValueComboBox)

    @pyqtSlot(int, name="on_fieldThreeValueComboBox_activated")
    def on_fieldThreeValueComboBox_activated(self, i):
        self.changeAttribute(i, self.fieldThreeNameComboBox,
                             self.fieldThreeValueComboBox)
class quickFinder(QObject):

    name = u"&Quick Finder"
    actions = None
    toolbar = None
    finders = {}

    loadingIcon = None

    def __init__(self, iface):
        QObject.__init__(self)
        self.iface = iface
        self.actions = {}
        self.finders = {}
        self.settings = MySettings()

        self._initFinders()

        self.iface.projectRead.connect(self._reloadFinders)
        self.iface.newProjectCreated.connect(self._reloadFinders)

        # translation environment
        self.plugin_dir = os.path.dirname(__file__)
        locale = QSettings().value("locale/userLocale")[0:2]
        localePath = os.path.join(self.plugin_dir, 'i18n', 'quickfinder_{0}.qm'.format(locale))
        if os.path.exists(localePath):
            self.translator = QTranslator()
            self.translator.load(localePath)
            QCoreApplication.installTranslator(self.translator)

    def initGui(self):
        self.actions['showSettings'] = QAction(
            QIcon(":/plugins/quickfinder/icons/settings.svg"),
            self.tr(u"&Settings"),
            self.iface.mainWindow())
        self.actions['showSettings'].triggered.connect(self.showSettings)
        self.iface.addPluginToMenu(self.name, self.actions['showSettings'])
        self.actions['help'] = QAction(
            QIcon(":/plugins/quickfinder/icons/help.svg"),
            self.tr("Help"),
            self.iface.mainWindow())
        self.actions['help'].triggered.connect(
            lambda: QDesktopServices().openUrl(
                QUrl("http://3nids.github.io/quickfinder")))
        self.iface.addPluginToMenu(self.name, self.actions['help'])
        self._initToolbar()
        self.rubber = QgsRubberBand(self.iface.mapCanvas())
        self.rubber.setColor(QColor(255, 255, 50, 200))
        self.rubber.setIcon(self.rubber.ICON_CIRCLE)
        self.rubber.setIconSize(15)
        self.rubber.setWidth(4)
        self.rubber.setBrushStyle(Qt.NoBrush)

    def unload(self):
        """ Unload plugin """
        for key in self.finders.keys():
            self.finders[key].close()
        for action in self.actions.itervalues():
            self.iface.removePluginMenu(self.name, action)
        if self.toolbar:
            del self.toolbar
        if self.rubber:
            self.iface.mapCanvas().scene().removeItem(self.rubber)
            del self.rubber

    def _initToolbar(self):
        """setup the plugin toolbar."""
        self.toolbar = self.iface.addToolBar(self.name)
        self.toolbar.setObjectName('mQuickFinderToolBar')
        self.searchAction = QAction(QIcon(":/plugins/quickfinder/icons/magnifier13.svg"),
                                     self.tr("Search"),
                                     self.toolbar)
        self.stopAction = QAction(
            QIcon(":/plugins/quickfinder/icons/wrong2.svg"),
            self.tr("Cancel"),
            self.toolbar)
        self.finderBox = FinderBox(self.finders, self.iface, self.toolbar)
        self.finderBox.searchStarted.connect(self.searchStarted)
        self.finderBox.searchFinished.connect(self.searchFinished)
        self.finderBoxAction = self.toolbar.addWidget(self.finderBox)
        self.finderBoxAction.setVisible(True)
        self.searchAction.triggered.connect(self.finderBox.search)
        self.toolbar.addAction(self.searchAction)
        self.stopAction.setVisible(False)
        self.stopAction.triggered.connect(self.finderBox.stop)
        self.toolbar.addAction(self.stopAction)
        self.toolbar.setVisible(True)

    def _initFinders(self):
        self.finders['geomapfish'] = GeomapfishFinder(self)
        self.finders['osm'] = OsmFinder(self)
        self.finders['project'] = ProjectFinder(self)
        for key in self.finders.keys():
            self.finders[key].message.connect(self.displayMessage)
        self.refreshProject()

    def _reloadFinders(self):
        for key in self.finders.keys():
            self.finders[key].close()
            self.finders[key].reload()
        self.refreshProject()

    @pyqtSlot(str, QgsMessageBar.MessageLevel)
    def displayMessage(self, message, level):
        self.iface.messageBar().pushMessage("QuickFinder", message, level)

    def showSettings(self):
        if ConfigurationDialog().exec_():
            self._reloadFinders()

    def searchStarted(self):
        self.searchAction.setVisible(False)
        self.stopAction.setVisible(True)

    def searchFinished(self):
        self.searchAction.setVisible(True)
        self.stopAction.setVisible(False)

    def refreshProject(self):
        if not self.finders['project'].activated:
            return
        if not self.settings.value("refreshAuto"):
            return
        nDays = self.settings.value("refreshDelay")
        # do not ask more ofen than 3 days
        askLimit = min(3, nDays)
        recentlyAsked = self.settings.value("refreshLastAsked") >= nDaysAgoIsoDate(askLimit)
        if recentlyAsked:
            return
        threshDate = nDaysAgoIsoDate(nDays)
        uptodate = True
        for search in self.finders['project'].searches.values():
            if search.dateEvaluated <= threshDate:
                uptodate = False
                break
        if uptodate:
            return
        self.settings.setValue("refreshLastAsked", nDaysAgoIsoDate(0))
        ret = QMessageBox(QMessageBox.Warning,
                          "Quick Finder",
                          QCoreApplication.translate("Auto Refresh", "Some searches are outdated. Do you want to refresh them ?"),
                          QMessageBox.Cancel | QMessageBox.Yes).exec_()
        if ret == QMessageBox.Yes:
            RefreshDialog(self.finders['project']).exec_()
Example #27
0
class RectangleMapTool(QgsMapToolEmitPoint):

    boxCreated = pyqtSignal(QgsRectangle)

    def __init__(self, canvas):
        self.canvas = canvas
        QgsMapToolEmitPoint.__init__(self, self.canvas)
        self.rubberBand = QgsRubberBand(self.canvas,
                                        QgsWkbTypes.PolygonGeometry)
        #self.rubberBand.setBorderColor(Qt.red)
        self.rubberBand.setBrushStyle(Qt.Dense6Pattern)
        self.rubberBand.setColor(Qt.red)
        self.rubberBand.setWidth(1)
        self.reset()

    def reset(self):
        self.startPoint = self.endPoint = None
        self.isEmittingPoint = False
        self.rubberBand.reset(QgsWkbTypes.PolygonGeometry)

    def canvasPressEvent(self, e):
        self.startPoint = self.toMapCoordinates(e.pos())
        self.endPoint = self.startPoint
        self.isEmittingPoint = True
        self.showRect(self.startPoint, self.endPoint)

    def canvasReleaseEvent(self, e):
        self.isEmittingPoint = False
        r = self.rectangle()
        if r is not None:
            self.boxCreated.emit(r)

    def canvasMoveEvent(self, e):
        if not self.isEmittingPoint:
            return

        self.endPoint = self.toMapCoordinates(e.pos())
        self.showRect(self.startPoint, self.endPoint)

    def showRect(self, startPoint, endPoint):
        self.rubberBand.reset(QgsWkbTypes.PolygonGeometry)
        if startPoint.x() == endPoint.x() or startPoint.y() == endPoint.y():
            return

        point1 = QgsPointXY(startPoint.x(), startPoint.y())
        point2 = QgsPointXY(startPoint.x(), endPoint.y())
        point3 = QgsPointXY(endPoint.x(), endPoint.y())
        point4 = QgsPointXY(endPoint.x(), startPoint.y())

        self.rubberBand.addPoint(point1, False)
        self.rubberBand.addPoint(point2, False)
        self.rubberBand.addPoint(point3, False)
        self.rubberBand.addPoint(point4, True)  # true to update canvas
        self.rubberBand.show()

    def rectangle(self):
        if self.startPoint is None or self.endPoint is None:
            return None
        elif self.startPoint.x() == self.endPoint.x() or self.startPoint.y(
        ) == self.endPoint.y():
            return None

        return QgsRectangle(self.startPoint, self.endPoint)
Example #28
0
class QuickFinder(QObject):

    name = u"&Quick Finder"
    actions = None
    toolbar = None
    finders = {}

    loadingIcon = None

    def __init__(self, iface):
        QObject.__init__(self)
        self.iface = iface
        self.actions = {}
        self.finders = {}
        self.settings = MySettings()
        self.rubber = None

        self._init_finders()

        self.iface.projectRead.connect(self._reload_finders)
        self.iface.newProjectCreated.connect(self._reload_finders)

        # translation environment
        self.plugin_dir = os.path.dirname(__file__)
        locale = QSettings().value("locale/userLocale")[0:2]
        localePath = os.path.join(self.plugin_dir, 'i18n', 'quickfinder_{0}.qm'.format(locale))
        if os.path.exists(localePath):
            self.translator = QTranslator()
            self.translator.load(localePath)
            QCoreApplication.installTranslator(self.translator)

    def initGui(self):
        self.actions['showSettings'] = QAction(
            QIcon(":/plugins/quickfinder/icons/settings.svg"),
            self.tr(u"&Settings"),
            self.iface.mainWindow())
        self.actions['showSettings'].triggered.connect(self.show_settings)
        self.iface.addPluginToMenu(self.name, self.actions['showSettings'])
        self.actions['help'] = QAction(
            QIcon(":/plugins/quickfinder/icons/help.svg"),
            self.tr("Help"),
            self.iface.mainWindow())
        self.actions['help'].triggered.connect(
            lambda: QDesktopServices().openUrl(
                QUrl("http://3nids.github.io/quickfinder")))
        self.iface.addPluginToMenu(self.name, self.actions['help'])
        self._init_toolbar()
        self.rubber = QgsRubberBand(self.iface.mapCanvas())
        self.rubber.setColor(QColor(255, 255, 50, 200))
        self.rubber.setIcon(self.rubber.ICON_CIRCLE)
        self.rubber.setIconSize(15)
        self.rubber.setWidth(4)
        self.rubber.setBrushStyle(Qt.NoBrush)

    def unload(self):
        """ Unload plugin """
        for key in self.finders.keys():
            self.finders[key].close()
        for action in self.actions.itervalues():
            self.iface.removePluginMenu(self.name, action)
        if self.toolbar:
            del self.toolbar
        if self.rubber:
            self.iface.mapCanvas().scene().removeItem(self.rubber)
            del self.rubber

    def _init_toolbar(self):
        """setup the plugin toolbar."""
        self.toolbar = self.iface.addToolBar(self.name)
        self.toolbar.setObjectName('mQuickFinderToolBar')
        self.search_action = QAction(QIcon(":/plugins/quickfinder/icons/magnifier13.svg"), self.tr("Search"), self.toolbar)
        self.stop_action = QAction(QIcon(":/plugins/quickfinder/icons/wrong2.svg"), self.tr("Cancel"), self.toolbar)
        self.finder_box = FinderBox(self.finders, self.iface, self.toolbar)
        self.finder_box.search_started.connect(self.search_started)
        self.finder_box.search_finished.connect(self.search_finished)
        self.finder_box_action = self.toolbar.addWidget(self.finder_box)
        self.finder_box_action.setVisible(True)
        self.search_action.triggered.connect(self.finder_box.search)
        self.toolbar.addAction(self.search_action)
        self.stop_action.setVisible(False)
        self.stop_action.triggered.connect(self.finder_box.stop)
        self.toolbar.addAction(self.stop_action)
        self.toolbar.setVisible(True)

    def _init_finders(self):
        self.finders['geomapfish'] = GeomapfishFinder(self)
        self.finders['osm'] = OsmFinder(self)
        self.finders['project'] = ProjectFinder(self)
        for key in self.finders.keys():
            self.finders[key].message.connect(self.display_message)
        self.refresh_project()

    def _reload_finders(self):
        for key in self.finders.keys():
            self.finders[key].close()
            self.finders[key].reload()
        self.refresh_project()

    @pyqtSlot(str, QgsMessageBar.MessageLevel)
    def display_message(self, message, level):
        self.iface.messageBar().pushMessage("QuickFinder", message, level)

    def show_settings(self):
        if ConfigurationDialog().exec_():
            self._reload_finders()

    def search_started(self):
        self.search_action.setVisible(False)
        self.stop_action.setVisible(True)

    def search_finished(self):
        self.search_action.setVisible(True)
        self.stop_action.setVisible(False)

    def refresh_project(self):
        if not self.finders['project'].activated:
            return
        if not self.settings.value("refreshAuto"):
            return
        n_days = self.settings.value("refreshDelay")
        # do not ask more ofen than 3 days
        ask_limit = min(3, n_days)
        recently_asked = self.settings.value("refreshLastAsked") >= n_days_ago_iso_date(ask_limit)
        if recently_asked:
            return
        thresh_date = n_days_ago_iso_date(n_days)
        uptodate = True
        for search in self.finders['project'].searches.values():
            if search.dateEvaluated <= thresh_date:
                uptodate = False
                break
        if uptodate:
            return
        self.settings.setValue("refreshLastAsked", n_days_ago_iso_date(0))
        ret = QMessageBox(QMessageBox.Warning,
                          "Quick Finder",
                          QCoreApplication.translate("Auto Refresh", "Some searches are outdated. Do you want to refresh them ?"),
                          QMessageBox.Cancel | QMessageBox.Yes).exec_()
        if ret == QMessageBox.Yes:
            RefreshDialog(self.finders['project']).exec_()
Example #29
0
class FinderBox(QComboBox):

    running = False
    to_finish = 0

    search_started = pyqtSignal()
    search_finished = pyqtSignal()

    def __init__(self, finders, iface, parent=None):
        self.iface = iface
        self.mapCanvas = iface.mapCanvas()
        self.marker = None
        self.rubber = QgsRubberBand(self.mapCanvas)
        self.rubber.setColor(QColor(255, 255, 50, 200))
        self.rubber.setIcon(self.rubber.ICON_CIRCLE)
        self.rubber.setIconSize(15)
        self.rubber.setWidth(4)
        self.rubber.setBrushStyle(Qt.NoBrush)

        QComboBox.__init__(self, parent)
        self.setEditable(True)
        self.setInsertPolicy(QComboBox.InsertAtTop)
        self.setMinimumHeight(27)
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)

        self.insertSeparator(0)
        self.lineEdit().returnPressed.connect(self.search)

        self.result_view = QTreeView()
        self.result_view.setHeaderHidden(True)
        self.result_view.setMinimumHeight(300)
        self.result_view.activated.connect(self.itemActivated)
        self.result_view.pressed.connect(self.itemPressed)
        self.setView(self.result_view)

        self.result_model = ResultModel(self)
        self.setModel(self.result_model)

        self.finders = finders
        for finder in list(self.finders.values()):
            finder.result_found.connect(self.result_found)
            finder.limit_reached.connect(self.limit_reached)
            finder.finished.connect(self.finished)

        self.clearButton = QPushButton(self)
        self.clearButton.setIcon(
            QIcon(":/plugins/quickfinder/icons/draft.svg"))
        self.clearButton.setText('')
        self.clearButton.setFlat(True)
        self.clearButton.setCursor(QCursor(Qt.ArrowCursor))
        self.clearButton.setStyleSheet('border: 0px; padding: 0px;')
        self.clearButton.clicked.connect(self.clear)

        layout = QHBoxLayout(self)
        self.setLayout(layout)
        layout.addStretch()
        layout.addWidget(self.clearButton)
        layout.addSpacing(20)

        button_size = self.clearButton.sizeHint()
        # frameWidth = self.lineEdit().style().pixelMetric(QtGui.QStyle.PM_DefaultFrameWidth)
        padding = button_size.width()  # + frameWidth + 1
        self.lineEdit().setStyleSheet('QLineEdit {padding-right: %dpx; }' %
                                      padding)

    def __del__(self):
        if self.rubber:
            self.iface.mapCanvas().scene().removeItem(self.rubber)
            del self.rubber

    #clear marker that the lonlat seted
    def clearMarker(self):
        self.mapCanvas.scene().removeItem(self.marker)
        self.marker = None

    def clearSelection(self):
        self.result_model.setSelected(None, self.result_view.palette())
        self.rubber.reset()
        #self.clearMarker()

    def clear(self):
        self.clearSelection()
        self.result_model.clearResults()
        self.lineEdit().setText('')

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Escape:
            self.clearSelection()
            self.clearMarker()
        QComboBox.keyPressEvent(self, event)

    def lnglatFinder(self, to_find):
        import re
        m = re.match(r'(?P<lon>-?\d*(.\d+))\s+(?P<lat>-?\d*(.\d+))', to_find)
        if not m:
            return False
        x = float(m.group('lon'))
        y = float(m.group('lat'))
        return self.zoomLnglat(x, y)

    def zoomLnglat(self, lng, lat):
        x, y = lng, lat

        canvas = self.mapCanvas
        currExt = canvas.extent()
        canvasCenter = currExt.center()
        dx = float(x) - canvasCenter.x()
        dy = float(y) - canvasCenter.y()

        xMin = currExt.xMinimum() + dx
        xMax = currExt.xMaximum() + dx
        yMin = currExt.yMinimum() + dy
        yMax = currExt.yMaximum() + dy

        rect = QgsRectangle(xMin, yMin, xMax, yMax)
        canvas.setExtent(rect)
        pt = QgsPointXY(float(x), float(y))
        self.marker = QgsVertexMarker(canvas)
        self.marker.setCenter(pt)
        self.marker.setIconSize(18)
        self.marker.setPenWidth(2)
        self.marker.setIconType(QgsVertexMarker.ICON_CROSS)
        canvas.refresh()
        return True

#    def geocodeFinder(self, to_finder):
#        print(to_finder[:2])
#        if not to_finder[:2] == 'b:':
#            return False
#
#        address = to_finder[2:]
#        url = MySettings().value("baiduUrl")
#        url = url + parse.quote(address)
#
#        response = request.urlopen(url)
#        content = response.read()
#        data = json.loads(content)
#        print(data)
#        lng, lat = (data['result']['location']['lng'], data['result']['location']['lat'])
#        from .cood_trans import bd09_to_wgs84
#        lng, lat = bd09_to_wgs84(lng, lat)
#        print(f'{lng}-{lat}')
#        return self.zoomLnglat(lng, lat)

    def search(self):
        #  self.geocode()
        if self.running:
            return

        to_find = self.lineEdit().text()
        if not to_find or to_find == '':
            return
#        if not (self.lnglatFinder(to_find) or self.geocodeFinder(to_find)):
        if not self.lnglatFinder(to_find):
            self.showPopup()

        self.running = True
        self.search_started.emit()

        self.clearSelection()
        self.result_model.clearResults()
        self.result_model.truncateHistory(MySettings().value("historyLength"))
        self.result_model.setLoading(True)

        QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)

        self.finders_to_start = []
        for finder in list(self.finders.values()):
            if finder.activated():
                self.finders_to_start.append(finder)

        bbox = self.mapCanvas.fullExtent()

        while len(self.finders_to_start) > 0:
            finder = self.finders_to_start[0]
            self.finders_to_start.remove(finder)
            self.result_model.addResult(finder.name)
            finder.start(to_find, bbox=bbox)

        # For case there is no finder activated
        self.finished(None)

    def stop(self):
        self.finders_to_start = []
        for finder in list(self.finders.values()):
            if finder.is_running():
                finder.stop()
        self.finished(None)

    def result_found(self, finder, layername, value, geometry, srid):
        self.result_model.addResult(finder.name, layername, value, geometry,
                                    srid)
        self.result_view.expandAll()

    def limit_reached(self, finder, layername):
        self.result_model.addEllipsys(finder.name, layername)

    def finished(self, finder):
        if len(self.finders_to_start) > 0:
            return
        for finder in list(self.finders.values()):
            if finder.is_running():
                return

        self.running = False
        self.search_finished.emit()

        self.result_model.setLoading(False)

        QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)

    def itemActivated(self, index):
        item = self.result_model.itemFromIndex(index)
        self.showItem(item)

    def itemPressed(self, index):
        item = self.result_model.itemFromIndex(index)
        if QApplication.mouseButtons() == Qt.LeftButton:
            self.showItem(item)

    def showItem(self, item):
        if isinstance(item, ResultItem):
            self.result_model.setSelected(item, self.result_view.palette())
            geometry = self.transform_geom(item)
            self.rubber.reset(geometry.type())
            self.rubber.setToGeometry(geometry, None)
            self.zoom_to_rubberband()
            return

        if isinstance(item, GroupItem):
            child = item.child(0)
            if isinstance(child, ResultItem):
                self.result_model.setSelected(item, self.result_view.palette())
                self.rubber.reset(child.geometry.type())
                for i in range(0, item.rowCount()):
                    geometry = self.transform_geom(item.child(i))
                    self.rubber.addGeometry(geometry, None)
                self.zoom_to_rubberband()
            return

        if item.__class__.__name__ == 'QStandardItem':
            self.clearSelection()

    def transform_geom(self, item):
        src_crs = QgsCoordinateReferenceSystem()
        src_crs.createFromSrid(item.srid)
        dest_crs = self.mapCanvas.mapSettings().destinationCrs()
        geom = QgsGeometry(item.geometry)
        geom.transform(
            QgsCoordinateTransform(src_crs, dest_crs, QgsProject.instance()))
        return geom

    def zoom_to_rubberband(self):
        geom = self.rubber.asGeometry()
        if geom:
            rect = geom.boundingBox()
            rect.scale(1.5)
            self.mapCanvas.setExtent(rect)
            self.mapCanvas.refresh()
Example #30
0
class AddPipeTool(QgsMapTool):
    def __init__(self, data_dock, params):
        QgsMapTool.__init__(self, data_dock.iface.mapCanvas())

        self.iface = data_dock.iface
        """:type : QgisInterface"""
        self.data_dock = data_dock
        """:type : DataDock"""
        self.params = params

        self.mouse_pt = None
        self.mouse_clicked = False
        self.first_click = False
        self.rubber_band = QgsRubberBand(self.canvas(), False)
        self.rubber_band.setColor(QColor(255, 128, 128))
        self.rubber_band.setWidth(1)
        self.rubber_band.setBrushStyle(Qt.Dense4Pattern)
        self.rubber_band.reset()

        self.snapper = None
        self.snapped_feat_id = None
        self.snapped_vertex = None
        self.snapped_vertex_nr = None
        self.vertex_marker = QgsVertexMarker(self.canvas())
        self.elev = None

        self.diameter_dialog = None

    def canvasPressEvent(self, event):
        if event.button() == Qt.RightButton:
            self.mouse_clicked = False

        if event.button() == Qt.LeftButton:
            self.mouse_clicked = True

    def canvasMoveEvent(self, event):

        self.mouse_pt = self.toMapCoordinates(event.pos())

        last_ix = self.rubber_band.numberOfVertices()

        self.rubber_band.movePoint(last_ix - 1,
                                   (self.snapped_vertex if self.snapped_vertex
                                    is not None else self.mouse_pt))

        elev = raster_utils.read_layer_val_from_coord(self.params.dem_rlay,
                                                      self.mouse_pt, 1)
        self.elev = elev
        if elev is not None:
            self.data_dock.lbl_elev_val.setText("{0:.2f}".format(self.elev))
        else:
            self.data_dock.lbl_elev_val.setText('-')

        # Mouse not clicked: snapping to closest vertex
        match = self.snapper.snapToMap(self.mouse_pt)

        if match.isValid():
            # Pipe starts from an existing vertex
            self.snapped_feat_id = match.featureId()
            self.snapped_vertex = match.point()
            self.snapped_vertex_nr = match.vertexIndex()

            self.vertex_marker.setCenter(self.snapped_vertex)
            self.vertex_marker.setColor(QColor(255, 0, 0))
            self.vertex_marker.setIconSize(10)
            self.vertex_marker.setIconType(QgsVertexMarker.ICON_CIRCLE)
            self.vertex_marker.setPenWidth(3)
            self.vertex_marker.show()
        #
        else:

            # It's a new, isolated pipe
            self.snapped_vertex = None
            self.snapped_feat_id = None
            self.vertex_marker.hide()

    def canvasReleaseEvent(self, event):

        # if not self.mouse_clicked:
        #     return
        if event.button() == Qt.LeftButton:

            # Update rubber bands
            self.rubber_band.addPoint(
                (self.snapped_vertex
                 if self.snapped_vertex is not None else self.mouse_pt), True)
            if self.first_click:
                self.rubber_band.addPoint(
                    (self.snapped_vertex if self.snapped_vertex is not None
                     else self.mouse_pt), True)

            self.first_click = not self.first_click

        elif event.button() == Qt.RightButton:

            # try:

            pipe_band_geom = self.rubber_band.asGeometry()

            # No rubber band geometry and feature snapped: pop the context menu
            if self.rubber_band.size(
            ) == 0 and self.snapped_feat_id is not None:
                menu = QMenu()
                section_action = menu.addAction('Section...')  # TODO: softcode
                diameter_action = menu.addAction(
                    'Change diameter...')  # TODO: softcode
                action = menu.exec_(self.iface.mapCanvas().mapToGlobal(
                    QPoint(event.pos().x(),
                           event.pos().y())))

                pipe_ft = vector_utils.get_feats_by_id(self.params.pipes_vlay,
                                                       self.snapped_feat_id)[0]
                if action == section_action:
                    if self.params.dem_rlay is None:
                        self.iface.messageBar().pushMessage(
                            Parameters.plug_in_name,
                            'No DEM selected. Cannot edit section.',
                            Qgis.Warning, 5)  # TODO: softcode
                    else:
                        # Check whether the pipe is all inside the DEM
                        pipe_pts = pipe_ft.geometry().asPolyline()

                        for pt in pipe_pts:
                            if not self.params.dem_rlay.extent().contains(pt):
                                self.iface.messageBar().pushMessage(
                                    Parameters.plug_in_name,
                                    'Some pipe vertices fall outside of the DEM. Cannot edit section.',
                                    Qgis.Warning, 5)  # TODO: softcode
                                return

                        # Check whether the start/end nodes have an elevation value
                        (start_node_ft,
                         end_node_ft) = NetworkUtils.find_start_end_nodes(
                             self.params, pipe_ft.geometry())
                        start_node_elev = start_node_ft.attribute(
                            Junction.field_name_elev)
                        end_node_elev = end_node_ft.attribute(
                            Junction.field_name_elev)
                        if start_node_elev == NULL or end_node_elev == NULL:
                            self.iface.messageBar().pushMessage(
                                Parameters.plug_in_name,
                                'Missing elevation value in start or end node attributes. Cannot edit section.',
                                Qgis.Warning, 5)  # TODO: softcode
                            return

                        pipe_dialog = PipeSectionDialog(
                            self.iface.mainWindow(), self.iface, self.params,
                            pipe_ft)
                        pipe_dialog.exec_()

                elif action == diameter_action:

                    old_diam = pipe_ft.attribute(Pipe.field_name_diameter)
                    self.diameter_dialog = DiameterDialog(
                        self.iface.mainWindow(), self.params, old_diam)
                    self.diameter_dialog.exec_()  # Exec creates modal dialog
                    new_diameter = self.diameter_dialog.get_diameter()
                    if new_diameter is None:
                        return

                    # Update pipe diameter
                    vector_utils.update_attribute(self.params.pipes_vlay,
                                                  pipe_ft,
                                                  Pipe.field_name_diameter,
                                                  new_diameter)

                    # Check if a valve is present
                    adj_valves = NetworkUtils.find_links_adjacent_to_link(
                        self.params, self.params.pipes_vlay, pipe_ft, True,
                        True, False)

                    if adj_valves['valves']:
                        self.iface.messageBar().pushMessage(
                            Parameters.plug_in_name,
                            'Valves detected on the pipe: need to update their diameters too.',
                            Qgis.Warning, 5)  # TODO: softcode

                return

            # There's a rubber band: create new pipe
            if pipe_band_geom is not None:

                #XPSS ADDED - transform rubber band CRS
                canvas_crs = self.canvas().mapSettings().destinationCrs()
                #pipes_vlay = Parameters().pipes_vlay()
                #print(pipes_vlay)
                qepanet_crs = self.params.pipes_vlay.sourceCrs()
                crs_transform = QgsCoordinateTransform(canvas_crs, qepanet_crs,
                                                       QgsProject.instance())
                pipe_band_geom.transform(crs_transform)

                # Finalize line
                rubberband_pts = pipe_band_geom.asPolyline()[:-1]

                if len(rubberband_pts) == 0:
                    self.iface.mapCanvas().refresh()
                    return

                rubberband_pts = self.remove_duplicated_point(rubberband_pts)

                if len(rubberband_pts) < 2:
                    self.rubber_band.reset()
                    return

                # Check whether the pipe points are located on existing nodes
                junct_nrs = [0]
                for p in range(1, len(rubberband_pts) - 1):
                    overlapping_nodes = NetworkUtils.find_overlapping_nodes(
                        self.params, rubberband_pts[p])
                    if overlapping_nodes['junctions'] or overlapping_nodes[
                            'reservoirs'] or overlapping_nodes['tanks']:
                        junct_nrs.append(p)

                junct_nrs.append(len(rubberband_pts) - 1)

                new_pipes_nr = len(junct_nrs) - 1

                new_pipes_fts = []
                new_pipes_eids = []

                for np in range(new_pipes_nr):

                    pipe_eid = NetworkUtils.find_next_id(
                        self.params.pipes_vlay, Pipe.prefix)  # TODO: softcode
                    #demand = float(self.data_dock.txt_pipe_demand.text())
                    length_units = 'm'  #TODO soft code
                    diameter = float(self.data_dock.cbo_pipe_dia.\
                                     currentText())
                    diameter_units = self.data_dock.cbo_pipe_dia_units.\
                        currentText()
                    #loss = float(self.data_dock.txt_pipe_loss.text())
                    #status = self.data_dock.cbo_pipe_status.currentText()
                    material = self.data_dock.cbo_pipe_mtl.currentText()
                    roughness = float(self.data_dock.txt_roughness.text())
                    #pipe_desc = self.data_dock.txt_pipe_desc.text()
                    #pipe_tag = self.data_dock.cbo_pipe_tag.currentText()
                    num_edu = 1
                    zone_id = 0
                    velocity = 0
                    velocity_units = 'm/s'
                    frictionloss = 0
                    frictionloss_units = 'm'

                    pipe_ft = LinkHandler.create_new_pipe(
                        self.params, pipe_eid, length_units, diameter,
                        diameter_units, 0, roughness, " ", material,
                        rubberband_pts[junct_nrs[np]:junct_nrs[np + 1] + 1],
                        True, " ", " ", num_edu, zone_id, velocity,
                        velocity_units, frictionloss, frictionloss_units)
                    self.rubber_band.reset()

                    new_pipes_fts.append(pipe_ft)
                    new_pipes_eids.append(pipe_eid)

                # emitter_coeff_s = self.data_dock.txt_junction_emit_coeff.text()
                #
                # if emitter_coeff_s is None or emitter_coeff_s == '':
                #     emitter_coeff = float(0)
                # else:
                #     emitter_coeff = float(self.data_dock.txt_junction_emit_coeff.text())

                # # Description
                # junction_desc = self.data_dock.txt_junction_desc.text()
                #
                # # Tag
                # junction_tag = self.data_dock.cbo_junction_tag.currentText()

                zone_end = 0
                pressure = 0
                pressure_units = self.data_dock.cbo_rpt_units_pressure.currentText(
                )

                # Create start and end node, if they don't exist
                (start_junction,
                 end_junction) = NetworkUtils.find_start_end_nodes(
                     self.params, new_pipes_fts[0].geometry())
                new_start_junction = None
                if not start_junction:
                    new_start_junction = rubberband_pts[0]
                    junction_eid = NetworkUtils.find_next_id(
                        self.params.junctions_vlay, Junction.prefix)
                    elev = raster_utils.read_layer_val_from_coord(
                        self.params.dem_rlay, new_start_junction, 1)
                    if elev is None:
                        elev = 0
                        # If elev is none, and the DEM is selected, it's better to inform the user
                        if self.params.dem_rlay is not None:
                            self.iface.messageBar().pushMessage(
                                Parameters.plug_in_name,
                                'Elevation value not available: element elevation set to 0.',
                                Qgis.Warning, 5)  # TODO: softcode
                    deltaz = float(0)
                    #j_demand = float(self.data_dock.txt_junction_demand.text())

                    # pattern = self.data_dock.cbo_junction_pattern.itemData(
                    #     self.data_dock.cbo_junction_pattern.currentIndex())
                    # if pattern is not None:
                    #     pattern_id = pattern.id
                    # else:
                    #     pattern_id = None
                    NodeHandler.create_new_junction(self.params,
                                                    new_start_junction,
                                                    junction_eid, elev, 0,
                                                    deltaz, None, 0, " ", " ",
                                                    zone_end, pressure,
                                                    pressure_units)

                (start_junction,
                 end_junction) = NetworkUtils.find_start_end_nodes(
                     self.params,
                     new_pipes_fts[len(new_pipes_fts) - 1].geometry())
                new_end_junction = None
                if not end_junction:
                    new_end_junction = rubberband_pts[len(rubberband_pts) - 1]
                    junction_eid = NetworkUtils.find_next_id(
                        self.params.junctions_vlay, Junction.prefix)
                    elev = raster_utils.read_layer_val_from_coord(
                        self.params.dem_rlay, new_end_junction, 1)
                    if elev is None:
                        elev = 0
                        # If elev is none, and the DEM is selected, it's better to inform the user
                        if self.params.dem_rlay is not None:
                            self.iface.messageBar().pushMessage(
                                Parameters.plug_in_name,
                                'Elevation value not available: element elevation set to 0.',
                                Qgis.Warning, 5)  # TODO: softcode
                    deltaz = float(0)

                    # pattern = self.data_dock.cbo_junction_pattern.itemData(
                    #     self.data_dock.cbo_junction_pattern.currentIndex())
                    # if pattern is not None:
                    #     pattern_id = pattern.id
                    # else:
                    #     pattern_id = None
                    NodeHandler.create_new_junction(self.params,
                                                    new_end_junction,
                                                    junction_eid, elev, 0,
                                                    deltaz, None, 0, " ", " ",
                                                    zone_end, pressure,
                                                    pressure_units)

                # If end or start node intersects a pipe, split it
                if new_start_junction:
                    for pipe_ft in self.params.pipes_vlay.getFeatures():
                        if pipe_ft.attribute(Pipe.field_name_eid) != new_pipes_eids[0] and\
                                        pipe_ft.geometry().distance(QgsGeometry.fromPointXY(new_start_junction)) < self.params.tolerance:
                            LinkHandler.split_pipe(self.params, pipe_ft,
                                                   new_start_junction)

                if new_end_junction:
                    for pipe_ft in self.params.pipes_vlay.getFeatures():
                        if pipe_ft.attribute(Pipe.field_name_eid) != new_pipes_eids[-1] and\
                                        pipe_ft.geometry().distance(QgsGeometry.fromPointXY(new_end_junction)) < self.params.tolerance:
                            LinkHandler.split_pipe(self.params, pipe_ft,
                                                   new_end_junction)

            self.iface.mapCanvas().refresh()

        # except Exception as e:
        #     self.rubber_band.reset()
        #     self.iface.messageBar().pushWarning('Cannot add new pipe to ' + self.params.pipes_vlay.name() + ' layer', repr(e))
        #     traceback.print_exc(file=sys.stdout)

    def keyReleaseEvent(self, event):
        if event.key() == Qt.Key_Escape:
            self.rubber_band.reset()

    def activate(self):

        self.update_snapper()

        # Editing
        if not self.params.junctions_vlay.isEditable():
            self.params.junctions_vlay.startEditing()
        if not self.params.pipes_vlay.isEditable():
            self.params.pipes_vlay.startEditing()

    def deactivate(self):

        # QgsProject.instance().setSnapSettingsForLayer(self.params.junctions_vlay.id(),
        #                                               True,
        #                                               QgsSnapper.SnapToVertex,
        #                                               QgsTolerance.MapUnits,
        #                                               0,
        #                                               True)
        #
        # QgsProject.instance().setSnapSettingsForLayer(self.params.reservoirs_vlay.id(),
        #                                               True,
        #                                               QgsSnapper.SnapToVertex,
        #                                               QgsTolerance.MapUnits,
        #                                               0,
        #                                               True)
        # QgsProject.instance().setSnapSettingsForLayer(self.params.tanks_vlay.id(),
        #                                               True,
        #                                               QgsSnapper.SnapToVertex,
        #                                               QgsTolerance.MapUnits,
        #                                               0,
        #                                               True)
        # QgsProject.instance().setSnapSettingsForLayer(self.params.pipes_vlay.id(),
        #                                               True,
        #                                               QgsSnapper.SnapToSegment,
        #                                               QgsTolerance.MapUnits,
        #                                               0,
        #                                               True)

        # self.rubber_band.reset()
        self.data_dock.btn_add_pipe.setChecked(False)

        self.canvas().scene().removeItem(self.vertex_marker)

    def isZoomTool(self):
        return False

    def isTransient(self):
        return False

    def isEditTool(self):
        return True

    def reset_marker(self):
        self.outlet_marker.hide()
        self.canvas().scene().removeItem(self.outlet_marker)

    def remove_duplicated_point(self, pts):

        # This is needed because the rubber band sometimes returns duplicated points
        purged_pts = [pts[0]]
        for p in enumerate(list(range(len(pts) - 1)), 1):
            if pts[p[0]] == pts[p[0] - 1]:
                continue
            else:
                purged_pts.append(pts[p[0]])

        return purged_pts

    def update_snapper(self):
        layers = {
            self.params.junctions_vlay: QgsSnappingConfig.Vertex,
            self.params.reservoirs_vlay: QgsSnappingConfig.Vertex,
            self.params.tanks_vlay: QgsSnappingConfig.Vertex,
            self.params.pipes_vlay: QgsSnappingConfig.VertexAndSegment
        }

        self.snapper = NetworkUtils.set_up_snapper(layers,
                                                   self.iface.mapCanvas(),
                                                   self.params.snap_tolerance)
        self.snapper.toggleEnabled()

    # Needed by Observable
    def update(self, observable):
        self.update_snapper()
class BanLocatorFilter(QgsLocatorFilter):

    HEADERS = {b'User-Agent': b'Mozilla/5.0 QGIS BAN Locator Filter'}

    message_emitted = pyqtSignal(str, str, Qgis.MessageLevel, QWidget)

    def __init__(self, iface: QgisInterface = None):
        """"
        :param filter_type: the type of filter
        :param locale_lang: the language of the locale.
        :param iface: QGIS interface, given when on the main thread (which will display/trigger results), None otherwise
        :param crs: if iface is not given, it shall be provided, see clone()
        """
        super().__init__()
        self.rubber_band = None
        self.feature_rubber_band = None
        self.iface = iface
        self.map_canvas = None
        self.settings = Settings()
        self.transform_4326 = None
        self.current_timer = None
        self.crs = None
        self.event_loop = None
        self.result_found = False
        self.search_delay = 0.5
        self.dbg_info("initialisation")

        if iface is not None:
            # happens only in main thread
            self.map_canvas = iface.mapCanvas()
            self.map_canvas.destinationCrsChanged.connect(
                self.create_transforms)

            self.rubber_band = QgsRubberBand(self.map_canvas,
                                             QgsWkbTypes.PointGeometry)
            self.rubber_band.setColor(QColor(255, 255, 50, 200))
            self.rubber_band.setIcon(self.rubber_band.ICON_CIRCLE)
            self.rubber_band.setIconSize(15)
            self.rubber_band.setWidth(4)
            self.rubber_band.setBrushStyle(Qt.NoBrush)

            self.feature_rubber_band = QgsRubberBand(
                self.map_canvas, QgsWkbTypes.PolygonGeometry)
            self.feature_rubber_band.setColor(QColor(255, 50, 50, 200))
            self.feature_rubber_band.setFillColor(QColor(255, 255, 50, 160))
            self.feature_rubber_band.setBrushStyle(Qt.SolidPattern)
            self.feature_rubber_band.setLineStyle(Qt.SolidLine)
            self.feature_rubber_band.setWidth(4)

            self.create_transforms()

    def name(self):
        return 'Recherche Adresse BAN'

    def clone(self):
        return BanLocatorFilter(self.iface)

    def priority(self):
        return self.settings.value('locations_priority')

    def displayName(self):
        return self.tr('Ban Adress Location')

    def prefix(self):
        return 'ban'

    def clearPreviousResults(self):
        self.rubber_band.reset(QgsWkbTypes.PointGeometry)
        self.feature_rubber_band.reset(QgsWkbTypes.PolygonGeometry)

        if self.current_timer is not None:
            self.current_timer.stop()
            self.current_timer.deleteLater()
            self.current_timer = None

    def hasConfigWidget(self):
        return True

    def openConfigWidget(self, parent=None):
        ConfigDialog(parent).exec_()

    def create_transforms(self):
        # this should happen in the main thread
        dst_crs = self.map_canvas.mapSettings().destinationCrs()
        src_crs_4326 = QgsCoordinateReferenceSystem('EPSG:4326')
        self.transform_4326 = QgsCoordinateTransform(src_crs_4326, dst_crs,
                                                     QgsProject.instance())

    def group_info(self, group: str) -> str:
        return TYPE_RESULT[group]

    @staticmethod
    def url_with_param(url, params) -> str:
        url = QUrl(url)
        q = QUrlQuery(url)
        for key, value in params.items():
            q.addQueryItem(key, value)
        url.setQuery(q)
        return url.url()

    def fetchResults(self, search: str, context: QgsLocatorContext,
                     feedback: QgsFeedback):
        try:
            self.dbg_info("start Ban locator search...")

            if len(search) < 5:
                return

            self.result_found = False

            url = 'https://api-adresse.data.gouv.fr/search/'
            params = {
                'q': str(search),
                'limit': str(self.settings.value('locations_limit'))
            }

            # Locations, WMS layers
            nam = NetworkAccessManager()
            feedback.canceled.connect(nam.abort)
            url = self.url_with_param(url, params)
            self.dbg_info(url)
            try:
                (response, content) = nam.request(url,
                                                  headers=self.HEADERS,
                                                  blocking=True)
                self.handle_response(response)
            except RequestsExceptionUserAbort:
                pass
            except RequestsException as err:
                self.info(err)

            if not self.result_found:
                result = QgsLocatorResult()
                result.filter = self
                result.displayString = self.tr('No result found.')
                result.userData = NoResult
                self.resultFetched.emit(result)

        except Exception as e:
            self.info(e, Qgis.Critical)
            exc_type, exc_obj, exc_traceback = sys.exc_info()
            filename = os.path.split(
                exc_traceback.tb_frame.f_code.co_filename)[1]
            self.info(
                '{} {} {}'.format(exc_type, filename, exc_traceback.tb_lineno),
                Qgis.Critical)
            self.info(
                traceback.print_exception(exc_type, exc_obj, exc_traceback),
                Qgis.Critical)

    def handle_response(self, response):
        try:
            if response.status_code != 200:
                if not isinstance(response.exception,
                                  RequestsExceptionUserAbort):
                    self.info(
                        "Error in main response with status code: {} from {}".
                        format(response.status_code, response.url))
                return

            data = json.loads(response.content.decode('utf-8'))
            # self.dbg_info(data)
            for loc in data['features']:
                importance = loc['properties']['importance']
                citycode = loc['properties']['citycode']
                score = loc['properties']['score']
                type_result = loc['properties']['type']
                label = loc['properties']['label']
                x = float(loc['geometry']['coordinates'][0])
                y = float(loc['geometry']['coordinates'][1])
                result = QgsLocatorResult()
                result.filter = self
                result.displayString = label
                result.group = self.group_info(type_result)
                result.icon = QIcon(
                    ":/plugins/swiss_locator/icons/ban_locator.png")
                result.userData = LocationResult(importance, citycode, score,
                                                 type_result, label, x, y)
                self.result_found = True
                self.resultFetched.emit(result)

        except Exception as e:
            self.info(str(e), Qgis.Critical)
            exc_type, exc_obj, exc_traceback = sys.exc_info()
            filename = os.path.split(
                exc_traceback.tb_frame.f_code.co_filename)[1]
            self.info(
                '{} {} {}'.format(exc_type, filename, exc_traceback.tb_lineno),
                Qgis.Critical)
            self.info(
                traceback.print_exception(exc_type, exc_obj, exc_traceback),
                Qgis.Critical)

    def triggerResult(self, result: QgsLocatorResult):
        # this should be run in the main thread, i.e. mapCanvas should not be None

        # remove any map tip
        self.clearPreviousResults()

        if type(result.userData) == NoResult:
            pass
        else:
            point = QgsGeometry.fromPointXY(result.userData.point)
            if not point:
                return

            point.transform(self.transform_4326)

            self.highlight(point, result.userData.type_result)

            if not self.settings.value('keep_marker_visible'):
                self.current_timer = QTimer()
                self.current_timer.timeout.connect(self.clearPreviousResults)
                self.current_timer.setSingleShot(True)
                self.current_timer.start(8000)

    def highlight(self, point, type_result=None):

        self.rubber_band.reset(QgsWkbTypes.PointGeometry)
        self.rubber_band.addGeometry(point, None)

        if type_result in ('housenumber', 'street'):
            zoom = float(self.settings.value('location_housenumber_zoom'))
        else:
            zoom = float(self.settings.value('location_default_zoom'))
        self.map_canvas.setCenter(point.asPoint())
        self.map_canvas.zoomScale(zoom)

    def info(self, msg="", level=Qgis.Info, emit_message: bool = False):
        self.logMessage(str(msg), level)
        if emit_message:
            self.message_emitted.emit(msg, level)

    def dbg_info(self, msg=""):
        if DEBUG:
            self.info(msg)