Ejemplo n.º 1
0
 def setFlags(self, flagDict, ptLayer, lLayer, polLayer, ctx, feedback):
     """
     Saves each flag to its layer, accordingly to its geometry primitive.
     :param flags: (dict) a map from offended feature ID to offenders
                   feature set.
     :param ctx: (QgsProcessingContext) context in which processing was run.
     :param feedback: (QgsProcessingFeedback) QGIS progress tracking
                      component.
     :return: (tuple-of-QgsVectorLayer) filled flag layers.
     """
     fh = FeatureHandler()
     gh = GeometryHandler()
     fields = self.getFlagFields()
     layerMap = {
         QgsWkbTypes.PointGeometry: ptLayer,
         QgsWkbTypes.LineGeometry: lLayer,
         QgsWkbTypes.PolygonGeometry: polLayer
     }
     for ruleName, flags in flagDict.items():
         flagText = self.tr('Rule "{name}" broken: {{text}}').format(
             name=ruleName)
         for flagList in flags.values():
             for flag in flagList:
                 geom = flag["geom"]
                 for g in gh.multiToSinglePart(geom):
                     newFeature = QgsFeature(fields)
                     newFeature["reason"] = flagText.format(
                         text=flag["text"])
                     newFeature.setGeometry(g)
                     layerMap[geom.type()].addFeature(
                         newFeature, QgsFeatureSink.FastInsert)
     return (ptLayer, lLayer, polLayer)
Ejemplo n.º 2
0
 def __init__(self, iface, parent=None):
     """
     Class constructor.
     """
     self.canvas = iface.mapCanvas()
     super(DsgRasterInfoTool, self).__init__(parent)
     self.setupUi(self)
     self.bandTooltipButton.setToolTip(self.tr("Show raster tooltip"))
     self.dynamicHistogramButton.setToolTip(
         self.tr("Dynamic histogram view"))
     self.valueSetterButton.setToolTip(
         self.
         tr("Set raster value from mouse click\nShift + Left Click + Mouse Drag: Selects a set of points and assigns raster value for each point"
            ))
     self.assignBandValueTool = None
     self.parent = parent
     self.splitter.hide()
     self.iface = iface
     self.timerMapTips = QTimer(self.canvas)
     self.geometryHandler = GeometryHandler(iface)
     self.addShortcuts()
     self.valueSetterButton.setEnabled(False)
     self.iface.mapCanvas().currentLayerChanged.connect(
         self.enableAssignValue)
     self.iface.actionToggleEditing().triggered.connect(
         self.enableAssignValue)
     self.iface.mapCanvas().mapToolSet.connect(self.enableAssignValue)
     self.valueSetterButton.toggled.connect(self.activateValueSetter)
     # self.rasterComboBox.currentIndexChanged.connect(self.enableAssignValue)
     # start currentLayer selection
     self.currentLayer = None
    def processAlgorithm(self, parameters, context, feedback):
        """
        Here is where the processing itself takes place.
        """
        geometryHandler = GeometryHandler()
        inputLyr = self.parameterAsVectorLayer(parameters, self.INPUT, context)
        if inputLyr is None:
            raise QgsProcessingException(
                self.invalidSourceError(parameters, self.INPUT))
        onlySelected = self.parameterAsBool(parameters, self.SELECTED, context)
        tol = self.parameterAsDouble(parameters, self.TOLERANCE, context)
        self.prepareFlagSink(parameters, inputLyr, QgsWkbTypes.Point, context)
        # Compute the number of steps to display within the progress bar and
        # get features from source
        featureList, total = self.getIteratorAndFeatureCount(
            inputLyr, onlySelected=onlySelected)

        for current, feat in enumerate(featureList):
            # Stop the algorithm if cancel button has been clicked
            if feedback.isCanceled():
                break
            outOfBoundsList = geometryHandler.getOutOfBoundsAngle(feat, tol)
            if outOfBoundsList:
                for item in outOfBoundsList:
                    flagText = self.tr(
                        'Feature from layer {0} with id={1} has angle of value {2} degrees, which is lesser than the tolerance of {3} degrees.'
                    ).format(inputLyr.name(), item['feat_id'], item['angle'],
                             tol)
                    self.flagFeature(item['geom'], flagText)
            # Update the progress bar
            feedback.setProgress(int(current * total))

        return {self.FLAGS: self.flag_id}
Ejemplo n.º 4
0
 def __init__(self, iface):
     """
     Tool Behaviours: (all behaviours start edition, except for rectangle one)
     1- Left Click: Clears previous selection, selects feature, sets feature layer as active layer. 
     The selection is done with the following priority: Point, Line then Polygon. 
     Selection is only done in visible layer.
     2- Control + Left Click: Adds to selection selected feature. This selection follows the priority in item 1.
     3- Right Click: Opens feature form
     4- Control + Right Click: clears selection and set feature's layer as activeLayer. activeLayer's definition
     follows priority of item 1;
     5- Shift + drag and drop: draws a rectangle, then features that intersect this rectangl'e are added to selection
     """
     self.iface = iface
     self.canvas = self.iface.mapCanvas()
     self.toolAction = None
     QgsMapTool.__init__(self, self.canvas)
     self.rubberBand = QgsRubberBand(self.canvas,
                                     QgsWkbTypes.PolygonGeometry)
     self.hoverRubberBand = QgsRubberBand(self.canvas,
                                          QgsWkbTypes.PolygonGeometry)
     mFillColor = QColor(254, 178, 76, 63)
     self.rubberBand.setColor(mFillColor)
     self.hoverRubberBand.setColor(QColor(255, 0, 0, 90))
     self.rubberBand.setWidth(1)
     self.reset()
     self.blackList = self.getBlackList()
     self.cursorChanged = False
     self.cursorChangingHotkey = Qt.Key_Alt
     self.menuHovered = False  # indicates hovering actions over context menu
     self.geometryHandler = GeometryHandler(iface=self.iface)
Ejemplo n.º 5
0
 def getGapsOfCoverageWithFrame(self,
                                coverage,
                                frameLyr,
                                context,
                                feedback=None,
                                onFinish=None):
     """
     Identifies all gaps inside coverage layer and between coverage and frame layer.
     :param coverage: (QgsVectorLayer) unified coverage layer.
     :param frameLyr: (QgsVectorLayer) frame layer.
     :param context: (QgsProcessingContext)
     :param feedback: (QgsProcessingFeedback) QGIS' object for progress tracking and controlling.
     :param onFinish: (list-of-str) list of alg names to be executed after difference alg.
     """
     # identify all holes in coverage layer first
     coverageHolesParam = {
         'INPUT': coverage,
         'FLAGS': 'memory:',
         'SELECTED': False
     }
     coverageHoles = processing.run('dsgtools:identifygaps',
                                    coverageHolesParam, None, feedback,
                                    context)['FLAGS']
     geometryHandler = GeometryHandler()
     gapSet = set()
     for feat in coverageHoles.getFeatures():
         for geom in geometryHandler.deaggregateGeometry(feat.geometry()):
             self.flagFeature(geom, self.tr('Gap in coverage layer'))
             gapSet.add(geom)
     # missing possible holes between coverage and frame, but gaps in coverage may cause invalid geometries
     # while executing difference alg. Since its already identified, "add" them to the coverage
     layerHandler = LayerHandler()
     filledCoverage = layerHandler.createAndPopulateUnifiedVectorLayer(
         [coverage, coverageHoles], QgsWkbTypes.Polygon)
     # dissolveParameters = {
     #     'INPUT' : filledCoverage,
     #     'FIELD':[],
     #     'OUTPUT':'memory:'
     # }
     # dissolveOutput = processing.run('native:dissolve', dissolveParameters, context = context)['OUTPUT']
     dissolveOutput = LayerHandler().runGrassDissolve(
         filledCoverage, context)
     differenceParameters = {
         'INPUT': frameLyr,
         'OVERLAY': dissolveOutput,
         'OUTPUT': 'memory:'
     }
     differenceOutput = processing.run('native:difference',
                                       differenceParameters, onFinish,
                                       feedback, context)
     for feat in differenceOutput['OUTPUT'].getFeatures():
         for geom in geometryHandler.deaggregateGeometry(feat.geometry()):
             if geom not in gapSet:
                 self.flagFeature(geom,
                                  self.tr('Gap in coverage with frame'))
Ejemplo n.º 6
0
 def __init__(self, iface, rasterLayer):
     """
     Tool Behaviours: (all behaviours start edition, except for rectangle one)
     1- Left Click: Creates a new point feature with the value from raster, according to selected attribute. 
     5- Shift + drag and drop: draws a rectangle, then features that intersect this rectangle are selected 
     and their value is set according to raster value and selected attribute.
     """
     self.iface = iface
     self.canvas = self.iface.mapCanvas()
     QgsMapTool.__init__(self, self.canvas)
     self.toolAction = None
     self.qgsMapToolEmitPoint = QgsMapToolEmitPoint(self.canvas)
     self.geometryHandler = GeometryHandler(iface)
     self.rasterLayer = rasterLayer
     self.setRubberbandParameters()
     self.reset()
     self.auxList = []
     self.decimals = self.getDecimals()
    def processAlgorithm(self, parameters, context, feedback):
        """
        Here is where the processing itself takes place.
        """
        geometryHandler = GeometryHandler()
        layerHandler = LayerHandler()
        inputLyrList = self.parameterAsLayerList(parameters, self.INPUTLAYERS,
                                                 context)
        if inputLyrList == []:
            raise QgsProcessingException(
                self.invalidSourceError(parameters, self.INPUTLAYERS))
        onlySelected = self.parameterAsBool(parameters, self.SELECTED, context)
        tol = self.parameterAsDouble(parameters, self.TOLERANCE, context)
        self.prepareFlagSink(parameters, inputLyrList[0], QgsWkbTypes.Point,
                             context)
        for lyr in inputLyrList:
            if feedback.isCanceled():
                break
            self.runIdentifyOutOfBoundsAngles(lyr, onlySelected, tol, context)
        epsg = inputLyrList[0].crs().authid().split(':')[-1]
        coverage = layerHandler.createAndPopulateUnifiedVectorLayer(
            inputLyrList, QgsWkbTypes.Point, epsg, onlySelected=onlySelected)
        cleanedCoverage = self.cleanCoverage(coverage, context)
        segmentDict = geometryHandler.getSegmentDict(cleanedCoverage)
        # Compute the number of steps to display within the progress bar and
        # get features from source
        # featureList, total = self.getIteratorAndFeatureCount(inputLyr)

        # for current, feat in enumerate(featureList):
        #     # Stop the algorithm if cancel button has been clicked
        #     if feedback.isCanceled():
        #         break
        #     outOfBoundsList = geometryHandler.getOutOfBoundsAngle(feat, tol)
        #     if outOfBoundsList:
        #         for item in outOfBoundsList:
        #             flagText = self.tr('Feature from layer {0} with id={1} has angle of value {2} degrees, which is lesser than the tolerance of {3} degrees.').format(inputLyr.name(), item['feat_id'], item['angle'], tol)
        #             self.flagFeature(item['geom'], flagText)
        #     # Update the progress bar
        #     feedback.setProgress(int(current * total))

        return {self.FLAGS: self.flag_id}
    def processAlgorithm(self, parameters, context, feedback):
        """
        Here is where the processing itself takes place.
        """
        geometryHandler = GeometryHandler()
        layerHandler = LayerHandler()
        inputLyr = self.parameterAsVectorLayer(parameters, self.INPUT, context)
        if inputLyr is None:
            raise QgsProcessingException(
                self.invalidSourceError(parameters, self.INPUT))
        isMulti = QgsWkbTypes.isMultiType(int(inputLyr.wkbType()))
        onlySelected = self.parameterAsBool(parameters, self.SELECTED, context)
        self.prepareFlagSink(parameters, inputLyr, QgsWkbTypes.Polygon,
                             context)
        # Compute the number of steps to display within the progress bar and
        # get features from source

        multiStepFeedback = QgsProcessingMultiStepFeedback(3, feedback)
        multiStepFeedback.setCurrentStep(0)
        lyr = self.overlayCoverage(inputLyr, context, multiStepFeedback)
        featureList, total = self.getIteratorAndFeatureCount(
            lyr
        )  #only selected is not applied because we are using an inner layer, not the original ones
        QgsProject.instance().removeMapLayer(lyr)
        geomDict = dict()
        multiStepFeedback.setCurrentStep(1)
        for current, feat in enumerate(featureList):
            # Stop the algorithm if cancel button has been clicked
            if feedback.isCanceled():
                break
            geom = feat.geometry()
            if isMulti and not geom.isMultipart():
                geom.convertToMultiType()
            geomKey = geom.asWkb()
            if geomKey not in geomDict:
                geomDict[geomKey] = []
            geomDict[geomKey].append(feat)
            # # Update the progress bar
            multiStepFeedback.setProgress(current * total)
        multiStepFeedback.setCurrentStep(2)
        total = 100 / len(geomDict) if len(geomDict) != 0 else 0
        for k, v in geomDict.items():
            if feedback.isCanceled():
                break
            if len(v) > 1:
                flagText = self.tr('Features from {0} overlap.').format(
                    inputLyr.name())
                self.flagFeature(v[0].geometry(), flagText)
            multiStepFeedback.setProgress(current * total)
        return {self.FLAGS: self.flag_id}
Ejemplo n.º 9
0
    def processAlgorithm(self, parameters, context, feedback):
        """
        Here is where the processing itself takes place.
        """
        geometryHandler = GeometryHandler()
        layerHandler = LayerHandler()
        inputLyr = self.parameterAsVectorLayer(parameters, self.INPUT, context)
        if inputLyr is None:
            raise QgsProcessingException(
                self.invalidSourceError(parameters, self.INPUT))
        isMulti = QgsWkbTypes.isMultiType(int(inputLyr.wkbType()))
        onlySelected = self.parameterAsBool(parameters, self.SELECTED, context)
        self.prepareFlagSink(parameters, inputLyr, QgsWkbTypes.Polygon,
                             context)
        # Compute the number of steps to display within the progress bar and
        # get features from source
        multiStepFeedback = QgsProcessingMultiStepFeedback(4, feedback)
        lyr = self.getGapLyr(inputLyr,
                             context,
                             multiStepFeedback,
                             onlySelected=onlySelected)
        featureList, total = self.getIteratorAndFeatureCount(
            lyr
        )  #only selected is not applied because we are using an inner layer, not the original ones
        QgsProject.instance().removeMapLayer(lyr)
        geomDict = dict()

        multiStepFeedback.setCurrentStep(3)
        for current, feat in enumerate(featureList):
            # Stop the algorithm if cancel button has been clicked
            if feedback.isCanceled():
                break
            attrList = feat.attributes()
            if attrList == len(attrList) * [None]:
                geom = feat.geometry()
                self.flagFeature(
                    geom,
                    self.tr('Gap in layer {0}.').format(inputLyr.name()))
            # # Update the progress bar
            multiStepFeedback.setProgress(current * total)
        return {self.FLAGS: self.flag_id}
Ejemplo n.º 10
0
    def processAlgorithm(self, parameters, context, feedback):
        """
        Here is where the processing itself takes place.
        """
        geometryHandler = GeometryHandler()
        layerHandler = LayerHandler()
        inputLyrList = self.parameterAsLayerList(parameters, self.INPUTLAYERS,
                                                 context)
        if inputLyrList == []:
            raise QgsProcessingException(
                self.invalidSourceError(parameters, self.INPUTLAYERS))
        frameLyr = self.parameterAsVectorLayer(parameters, self.FRAMELAYER,
                                               context)
        if frameLyr and frameLyr in inputLyrList:
            raise QgsProcessingException(
                self.invalidSourceError(parameters, self.FRAMELAYER))
        isMulti = True
        for inputLyr in inputLyrList:
            isMulti &= QgsWkbTypes.isMultiType(int(inputLyr.wkbType()))
        onlySelected = self.parameterAsBool(parameters, self.SELECTED, context)
        self.prepareFlagSink(parameters, inputLyrList[0], QgsWkbTypes.Polygon,
                             context)
        # Compute the number of steps to display within the progress bar and
        # get features from source

        coverage = layerHandler.createAndPopulateUnifiedVectorLayer(
            inputLyrList, QgsWkbTypes.Polygon, onlySelected=onlySelected)
        lyr = self.overlayCoverage(coverage, context)
        if frameLyr:
            self.getGapsOfCoverageWithFrame(lyr, frameLyr, context)
        featureList, total = self.getIteratorAndFeatureCount(
            lyr
        )  #only selected is not applied because we are using an inner layer, not the original ones
        geomDict = self.getGeomDict(featureList, isMulti, feedback, total)
        self.raiseFlags(geomDict, feedback)
        QgsProject.instance().removeMapLayer(lyr)
        return {self.FLAGS: self.flag_id}
Ejemplo n.º 11
0
class AssignBandValueTool(QgsMapTool):
    def __init__(self, iface, rasterLayer):
        """
        Tool Behaviours: (all behaviours start edition, except for rectangle one)
        1- Left Click: Creates a new point feature with the value from raster, according to selected attribute. 
        5- Shift + drag and drop: draws a rectangle, then features that intersect this rectangle are selected 
        and their value is set according to raster value and selected attribute.
        """
        self.iface = iface
        self.canvas = self.iface.mapCanvas()
        QgsMapTool.__init__(self, self.canvas)
        self.toolAction = None
        self.qgsMapToolEmitPoint = QgsMapToolEmitPoint(self.canvas)
        self.geometryHandler = GeometryHandler(iface)
        self.rasterLayer = rasterLayer
        self.setRubberbandParameters()
        self.reset()
        self.auxList = []
        self.decimals = self.getDecimals()

    def getDecimals(self):
        settings = QSettings()
        settings.beginGroup('PythonPlugins/DsgTools/Options')
        decimals = settings.value('decimals')
        if decimals:
            return int(decimals)
        else:
            return 0

    def getSuppressOptions(self):
        qgisSettings = QSettings()
        qgisSettings.beginGroup('qgis/digitizing')
        setting = qgisSettings.value('disable_enter_attribute_values_dialog')
        qgisSettings.endGroup()
        return setting

    def setRubberbandParameters(self):
        self.rubberBand = QgsRubberBand(self.canvas,
                                        QgsWkbTypes.PolygonGeometry)
        self.hoverRubberBand = QgsRubberBand(self.canvas,
                                             QgsWkbTypes.PolygonGeometry)
        mFillColor = QColor(254, 178, 76, 63)
        self.rubberBand.setColor(mFillColor)
        self.hoverRubberBand.setColor(QColor(255, 0, 0, 90))
        self.rubberBand.setWidth(1)

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

    def canvasPressEvent(self, e):
        """
        Method used to build rectangle if shift is held, otherwise, feature select/deselect and identify is done.
        :param e: (QgsMouseEvent) mouse event.
        """
        if e.button() == QtCore.Qt.LeftButton:
            self.auxList = []
            if QApplication.keyboardModifiers() == QtCore.Qt.ShiftModifier:
                self.isEmittingPoint = True
                self.startPoint = self.toMapCoordinates(e.pos())
                self.endPoint = self.startPoint
                self.isEmittingPoint = True
                self.showRect(self.startPoint, self.endPoint)

    def canvasMoveEvent(self, e):
        """
        Used only on rectangle select.
        """
        if not self.isEmittingPoint:
            return
        self.endPoint = self.toMapCoordinates(e.pos())
        self.showRect(self.startPoint, self.endPoint)

    def showRect(self, startPoint, endPoint):
        """
        Builds rubberband rect.
        """
        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):
        """
        Builds rectangle from self.startPoint and self.endPoint
        """
        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)

    def setAction(self, action):
        self.toolAction = action
        self.toolAction.setCheckable(True)

    def canvasReleaseEvent(self, e):
        """
        After the rectangle is built, here features are selected.
        """
        # tool was planned to work on left click
        if e.button() == QtCore.Qt.LeftButton:
            layer = self.iface.mapCanvas().currentLayer()
            if QApplication.keyboardModifiers() == QtCore.Qt.ShiftModifier:
                self.isEmittingPoint = False
                r = self.rectangle()
                if r is None:
                    return
                bbRect = self.canvas.mapSettings().mapToLayerCoordinates(
                    layer, r)
                self.rubberBand.hide()
                #select all stuff
                layer.selectByIds([])  #portar para o feature handler
                layer.selectByRect(bbRect, True)
                #mudar depois para o dsgmothafucka
                featDict = dict()
                pointDict = dict()
                for feat in layer.selectedFeatures():
                    featDict[feat.id()] = feat
                    pointDict[feat.id()] = feat.geometry()
                pixelValueDict = self.getPixelValueFromPointDict(
                    pointDict, self.rasterLayer)
                for idx in pointDict:
                    value = pixelValueDict[idx]
                    if value:
                        self.auxList.append({
                            'featId': idx,
                            'feat': featDict[idx],
                            'value': value
                        })
            else:
                value, pointGeom = self.getPixelValue(self.rasterLayer)
                if value:
                    self.auxList.append({'geom': pointGeom, 'value': value})
            #create context menu to select attribute
            if self.auxList:
                self.createContextMenuOnPosition(e, layer)

    def createContextMenuOnPosition(self, e, layer):
        menu = QMenu()
        callbackDict = dict()
        fieldList = [
            field.name() for field in layer.fields() if field.isNumeric()
        ]
        for field in fieldList:
            action = menu.addAction(field)
            callback = partial(self.handleFeatures, field, layer)
            action.triggered.connect(callback)
        menu.exec_(self.canvas.viewport().mapToGlobal(e.pos()))

    def handleFeatures(self, selectedField, layer):
        layer.startEditing()
        for item in self.auxList:
            if 'featId' in item:
                feat = item['feat']
                idx = feat.fieldNameIndex(selectedField)
                feat.setAttribute(idx, item['value'])
                layer.updateFeature(feat)
            else:
                self.geometryHandler.reprojectFeature(item['geom'],
                                                      layer.crs())
                feature = QgsVectorLayerUtils.createFeature(
                    layer, item['geom'])
                self.addFeature(feature, layer, selectedField, item['value'])
        self.auxList = []
        self.canvas.refresh()

    def addFeature(self, feature, layer, field, pointValue):
        fields = layer.fields()
        provider = layer.dataProvider()
        for i in range(fields.count()):
            value = provider.defaultValue(
                i) if fields[i].name() != field else pointValue
            if value is not None:
                feature.setAttribute(i, value)
        form = QgsAttributeDialog(layer, feature, False)
        form.setMode(int(QgsAttributeForm.AddFeatureMode))
        formSuppress = layer.editFormConfig().suppress()
        if formSuppress == QgsEditFormConfig.SuppressDefault:
            if self.getSuppressOptions(
            ):  #this is calculated every time because user can switch options while using tool
                layer.addFeature(feature)
            else:
                if not form.exec_():
                    feature.setAttributes(form.feature().attributes())
        elif formSuppress == QgsEditFormConfig.SuppressOff:
            if not form.exec_():
                feature.setAttributes(form.feature().attributes())
        else:
            layer.addFeature(feature)

    def getCursorRect(self, e):
        """
        Calculates small cursor rectangle around mouse position. Used to facilitate operations
        """
        p = self.toMapCoordinates(e.pos())
        w = self.canvas.mapUnitsPerPixel() * 10
        return QgsRectangle(p.x() - w, p.y() - w, p.x() + w, p.y() + w)

    def deactivate(self):
        """
        Deactivate tool.
        """
        QApplication.restoreOverrideCursor()
        self.hoverRubberBand.reset(QgsWkbTypes.PolygonGeometry)
        try:
            if self.toolAction:
                self.toolAction.setChecked(False)
            if self is not None:
                QgsMapTool.deactivate(self)
                # self.canvas.unsetMapTool(self)
        except:
            pass

    def activate(self):
        """
        Activate tool.
        """
        if self.toolAction:
            self.toolAction.setChecked(True)
        QgsMapTool.activate(self)
        # self.iface.mapCanvas().setMapTool(self)
        layer = self.iface.mapCanvas().currentLayer()
        if not layer or not isinstance(layer, QgsVectorLayer):
            self.iface.messageBar().pushMessage(
                self.tr("Warning"),
                self.tr("Select a point vector layer as the active layer"),
                level=Qgis.Warning,
                duration=5)
            self.deactivate()

    def getPixelValue(self, rasterLayer):
        mousePos = self.qgsMapToolEmitPoint.toMapCoordinates(
            self.canvas.mouseLastXY())
        mousePosGeom = QgsGeometry.fromPointXY(mousePos)
        return self.getPixelValueFromPoint(mousePosGeom,
                                           rasterLayer), mousePosGeom

    def getPixelValueFromPoint(self,
                               mousePosGeom,
                               rasterLayer,
                               fromCanvas=True):
        """
        
        """
        rasterCrs = rasterLayer.crs()
        # if fromCanvas:
        #     self.geometryHandler.reprojectFeature(mousePosGeom, rasterCrs, QgsProject.instance().crs())
        # else:
        mousePosGeom = QgsGeometry(mousePosGeom)
        self.geometryHandler.reprojectFeature(mousePosGeom, rasterCrs,
                                              self.canvas.currentLayer().crs())
        mousePos = mousePosGeom.asMultiPoint()[0] if mousePosGeom.isMultipart(
        ) else mousePosGeom.asPoint()
        # identify pixel(s) information
        i = rasterLayer.dataProvider().identify(mousePos,
                                                QgsRaster.IdentifyFormatValue)
        if i.isValid():
            value = list(i.results().values())[0]
            if value:
                value = int(value) if self.decimals == 0 else round(
                    value, self.decimals)
            return value
        else:
            return None

    def getPixelValueFromPointDict(self, pointDict, rasterLayer):
        """
        pointDict = {'pointId':QgsGeometry}

        returns {'pointId': value}
        """
        return {
            key: self.getPixelValueFromPoint(value,
                                             rasterLayer,
                                             fromCanvas=False)
            for key, value in pointDict.items()
        }  #no python3 eh items()
Ejemplo n.º 12
0
class GenericSelectionTool(QgsMapTool):
    def __init__(self, iface):
        """
        Tool Behaviours: (all behaviours start edition, except for rectangle one)
        1- Left Click: Clears previous selection, selects feature, sets feature layer as active layer. 
        The selection is done with the following priority: Point, Line then Polygon. 
        Selection is only done in visible layer.
        2- Control + Left Click: Adds to selection selected feature. This selection follows the priority in item 1.
        3- Right Click: Opens feature form
        4- Control + Right Click: clears selection and set feature's layer as activeLayer. activeLayer's definition
        follows priority of item 1;
        5- Shift + drag and drop: draws a rectangle, then features that intersect this rectangl'e are added to selection
        """
        self.iface = iface
        self.canvas = self.iface.mapCanvas()
        self.toolAction = None
        QgsMapTool.__init__(self, self.canvas)
        self.rubberBand = QgsRubberBand(self.canvas,
                                        QgsWkbTypes.PolygonGeometry)
        self.hoverRubberBand = QgsRubberBand(self.canvas,
                                             QgsWkbTypes.PolygonGeometry)
        mFillColor = QColor(254, 178, 76, 63)
        self.rubberBand.setColor(mFillColor)
        self.hoverRubberBand.setColor(QColor(255, 0, 0, 90))
        self.rubberBand.setWidth(1)
        self.reset()
        self.blackList = self.getBlackList()
        self.cursorChanged = False
        self.cursorChangingHotkey = Qt.Key_Alt
        self.menuHovered = False  # indicates hovering actions over context menu
        self.geometryHandler = GeometryHandler(iface=self.iface)

    def addTool(self, manager, callback, parentMenu, iconBasePath):
        icon_path = iconBasePath + '/genericSelect.png'
        toolTip = self.tr(
            "DSGTools: Generic Selector\nLeft Click: select feature's layer and put it on edit mode\nRight Click: Open feature's form\nControl+Left Click: add/remove feature from selection\nShift+Left Click+drag and drop: select all features that intersects rubberband."
        )
        action = manager.add_action(icon_path,
                                    text=self.tr('DSGTools: Generic Selector'),
                                    callback=callback,
                                    add_to_menu=False,
                                    add_to_toolbar=True,
                                    withShortcut=True,
                                    tooltip=toolTip,
                                    parentToolbar=parentMenu,
                                    isCheckable=True)
        self.setAction(action)

    def keyPressEvent(self, e):
        """
        Reimplemetation of keyPressEvent() in order to handle cursor changing hotkey (Alt).
        """
        if e.key() == self.cursorChangingHotkey and not self.cursorChanged:
            self.cursorChanged = True
            QApplication.setOverrideCursor(QCursor(Qt.PointingHandCursor))
        else:
            self.cursorChanged = False
            QApplication.restoreOverrideCursor()

    def getBlackList(self):
        settings = QSettings()
        settings.beginGroup('PythonPlugins/DsgTools/Options')
        valueList = settings.value('valueList')
        if valueList:
            valueList = valueList.split(';')
            return valueList
        else:
            return ['moldura']

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

    def keyPressEvent(self, e):
        """
        Reimplemetation of keyPressEvent() in order to handle cursor changing hotkey (F2).
        """
        if e.key() == self.cursorChangingHotkey and not self.cursorChanged:
            self.cursorChanged = True
            QApplication.setOverrideCursor(QCursor(Qt.PointingHandCursor))
        else:
            self.cursorChanged = False
            QApplication.restoreOverrideCursor()

    def canvasMoveEvent(self, e):
        """
        Used only on rectangle select.
        """
        if self.menuHovered:
            # deactivates rubberband when the context menu is "destroyed"
            self.hoverRubberBand.reset(QgsWkbTypes.PolygonGeometry)
        if not self.isEmittingPoint:
            return
        self.endPoint = self.toMapCoordinates(e.pos())
        self.showRect(self.startPoint, self.endPoint)

    def showRect(self, startPoint, endPoint):
        """
        Builds rubberband rect.
        """
        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):
        """
        Builds rectangle from self.startPoint and self.endPoint
        """
        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)

    def setAction(self, action):
        self.toolAction = action
        self.toolAction.setCheckable(True)

    def canvasReleaseEvent(self, e):
        """
        After the rectangle is built, here features are selected.
        """
        if QApplication.keyboardModifiers() == Qt.ShiftModifier:
            firstGeom = self.checkSelectedLayers()
            self.isEmittingPoint = False
            r = self.rectangle()
            if r is None:
                return
            layers = self.canvas.layers()
            for layer in layers:
                #ignore layers on black list and features that are not vector layers
                if not isinstance(layer, QgsVectorLayer) or (
                        self.layerHasPartInBlackList(layer.name())):
                    continue
                if firstGeom is not None and layer.geometryType() != firstGeom:
                    # if there are features already selected, shift will only get the same type geometry
                    # if more than one ty of geometry is present, only the strongest will be selected
                    continue
                #builds bbRect and select from layer, adding selection
                bbRect = self.canvas.mapSettings().mapToLayerCoordinates(
                    layer, r)
                layer.selectByRect(bbRect,
                                   behavior=QgsVectorLayer.AddToSelection)
            self.rubberBand.hide()

    def canvasPressEvent(self, e):
        """
        Method used to build rectangle if shift is held, otherwise, feature select/deselect and identify is done.
        """
        if QApplication.keyboardModifiers() == Qt.ShiftModifier:
            self.isEmittingPoint = True
            self.startPoint = self.toMapCoordinates(e.pos())
            self.endPoint = self.startPoint
            self.isEmittingPoint = True
            self.showRect(self.startPoint, self.endPoint)
        else:
            self.isEmittingPoint = False
            self.createContextMenu(e)

    def getCursorRect(self, e):
        """
        Calculates small cursor rectangle around mouse position. Used to facilitate operations
        """
        p = self.toMapCoordinates(e.pos())
        w = self.canvas.mapUnitsPerPixel() * 10
        return QgsRectangle(p.x() - w, p.y() - w, p.x() + w, p.y() + w)

    def layerHasPartInBlackList(self, lyrName):
        """
        Verifies if terms in black list appear on lyrName
        """
        for item in self.getBlackList():
            if item.lower() in lyrName.lower():
                return True
        return False

    def getPrimitiveDict(self, e, hasControlModifier=False):
        """
        Builds a dict with keys as geometryTypes of layer, which are Qgis.Point (value 0), Qgis.Line (value 1) or Qgis.Polygon (value 2),
        and values as layers from self.iface.mapCanvas().layers(). When self.iface.mapCanvas().layers() is called, a list of
        layers ordered according to lyr order in TOC is returned.
        """
        #these layers are ordered by view order
        primitiveDict = dict()
        firstGeom = self.checkSelectedLayers()
        visibleLayers = QgsProject.instance().layerTreeRoot().checkedLayers()
        for lyr in self.iface.mapCanvas().layers():  #ordered layers
            #layer types other than VectorLayer are ignored, as well as layers in black list and layers that are not visible
            if not isinstance(lyr,
                              QgsVectorLayer) or (self.layerHasPartInBlackList(
                                  lyr.name())) or lyr not in visibleLayers:
                continue
            if hasControlModifier and (not firstGeom) and (
                    not primitiveDict or lyr.geometryType() < firstGeom):
                firstGeom = lyr.geometryType()
            geomType = lyr.geometryType()
            if geomType not in primitiveDict:
                primitiveDict[geomType] = []
            #removes selection
            if (not hasControlModifier and e.button() == Qt.LeftButton) or (
                    hasControlModifier and e.button() == Qt.RightButton):
                lyr.removeSelection()
            primitiveDict[geomType].append(lyr)
        if hasControlModifier and firstGeom in [0, 1, 2]:
            return {firstGeom: primitiveDict[firstGeom]}
        else:
            return primitiveDict

    def deactivate(self):
        """
        Deactivate tool.
        """
        QApplication.restoreOverrideCursor()
        self.hoverRubberBand.reset(QgsWkbTypes.PolygonGeometry)
        try:
            self.rubberBand.reset()
            if self.toolAction:
                self.toolAction.setChecked(False)
            if self is not None:
                QgsMapTool.deactivate(self)
        except:
            pass

    def activate(self):
        """
        Activate tool.
        """
        if self.toolAction:
            self.toolAction.setChecked(True)
        QgsMapTool.activate(self)

    def setSelectionFeature(self,
                            layer,
                            feature,
                            selectAll=False,
                            setActiveLayer=False):
        """
        Selects a given feature on canvas. 
        :param layer: (QgsVectorLayer) layer containing the target feature.
        :param feature: (QgsFeature) taget feature to be selected.
        :param selectAll: (bool) indicates whether or not this fuction was called from a select all command.
                          so it doesn't remove selection from those that are selected already from the list.
        :param setActiveLayer: (bool) indicates whether method should set layer as active.
        """
        idList = layer.selectedFeatureIds()
        self.iface.setActiveLayer(layer)
        layer.startEditing()
        featId = feature.id()
        if featId not in idList:
            idList.append(featId)
        elif not selectAll:
            idList.pop(idList.index(featId))
        layer.selectByIds(idList)
        if setActiveLayer:
            layer.startEditing()
            self.iface.setActiveLayer(layer)
        return

    def setSelectionListFeature(self, dictLayerFeature, selectAll=True):
        """
        Selects all features on canvas of a given dict.        
        :param dictLayerFeature: (dict) dict of layers/features to be selected.
        :param selectAll: (bool) indicates if "All"-command comes from a "Select All". In that case, selected features
                          won't be deselected.
        """
        for layer, listOfFeatures in dictLayerFeature.items():
            geomType = layer.geometryType()
            # ID list of features already selected
            idList = layer.selectedFeatureIds()
            # restart feature ID list for each layer
            featIdList = []
            for feature in listOfFeatures:
                featId = feature.id()
                if featId not in idList:
                    idList.append(featId)
                elif not selectAll:
                    idList.pop(idList.index(featId))
            layer.selectByIds(idList)
            layer.startEditing()
        # last layer is set active and
        self.iface.setActiveLayer(layer)

    def openMultipleFeatureForm(self, dictLayerFeature):
        """
        Opens all features Feature Forms of a given list.
        :param dictLayerFeature: (dict) dict of layers/features to have their feature form exposed.
        """
        for layer, features in dictLayerFeature.items():
            for feat in features:
                self.iface.openFeatureForm(layer, feat, showModal=False)

    def filterStrongestGeometry(self, dictLayerFeature):
        """
        Filter a given dict of features for its strongest geometry.
        :param dictLayerFeature: (dict) a dict of layers and its features to be filtered.
        :return: (dict) filtered dict with only layers of the strongest geometry on original dict.
        """
        strongest_geometry = 3
        outDict = dict()
        if dictLayerFeature:
            for lyr in dictLayerFeature:
                # to retrieve strongest geometry value
                if strongest_geometry > lyr.geometryType():
                    strongest_geometry = lyr.geometryType()
                if strongest_geometry == 0:
                    break
        for lyr in dictLayerFeature:
            if lyr.geometryType() == strongest_geometry:
                outDict[lyr] = dictLayerFeature[lyr]
        return outDict

    def createRubberBand(self, feature, layer, geom):
        """
        Creates a rubber band around from a given a standard feature string.
        :param feature: taget feature to be highlighted 
        :param layer: layer containing the target feature
        :param geom: int indicating geometry type of target feature
        """
        if geom == 0:
            self.hoverRubberBand.reset(QgsWkbTypes.PointGeometry)
        elif geom == 1:
            self.hoverRubberBand.reset(QgsWkbTypes.LineGeometry)
        else:
            self.hoverRubberBand.reset(QgsWkbTypes.PolygonGeometry)
        self.hoverRubberBand.addGeometry(feature.geometry(), layer)
        # to inform the code that menu has been hovered over
        self.menuHovered = True

    def createMultipleRubberBand(self, dictLayerFeature):
        """
        Creates rubberbands around features.
        :param dictLayerFeature: (dict) dict of layer/features to have rubberbands built around.
        """
        # only one type of geometry at a time will have rubberbands around it
        geom = list(dictLayerFeature.keys())[0].geometryType()
        if geom == 0:
            self.hoverRubberBand.reset(QgsWkbTypes.PointGeometry)
        elif geom == 1:
            self.hoverRubberBand.reset(QgsWkbTypes.LineGeometry)
        else:
            self.hoverRubberBand.reset(QgsWkbTypes.PolygonGeometry)
        for layer, features in dictLayerFeature.items():
            for feat in features:
                self.hoverRubberBand.addGeometry(feat.geometry(), layer)
        self.menuHovered = True

    def checkSelectedLayers(self):
        """
        Checks if there are layers selected on canvas. If there are, returns the geometry type of
        selected feature(s). If more than one type of feature is selected, the "strongest" geometry
        is returned.
        """
        geom = None
        for layer in self.iface.mapCanvas().layers():
            if isinstance(layer, QgsVectorLayer):
                selection = layer.selectedFeatures()
                if len(selection):
                    if geom == None:
                        geom = layer.geometryType()
                        continue
                    elif layer.geometryType() < geom:
                        geom = layer.geometryType()
                        continue
        return geom

    def addCallBackToAction(self,
                            action,
                            onTriggeredAction,
                            onHoveredAction=None):
        """
        Adds action the command to the action. If onHoveredAction is given, signal "hovered" is applied with given action.
        :param action: (QAction) associated with target context menu.
        :param onTriggeredAction: (object) action to be executed when the given action is triggered.
        :param onHoveredAction: (object) action to be executed whilst the given action is hovered.
        """
        action.triggered.connect(onTriggeredAction)
        if onHoveredAction:
            action.hovered.connect(onHoveredAction)

    def getCallback(self, e, layer, feature, geomType=None, selectAll=True):
        """
        Gets the callback for an action.
        :param e: (QMouseEvent) mouse event on canvas.
        :param layer: (QgsVectorLayer) layer to be treated.
        :param feature: (QgsFeature) feature to be treated.
        :param geomType: (int) code indicating layer geometry type. It is retrieved OTF in case it's not given.
        :return: (tuple-of function_lambda) callbacks for triggered and hovered signals.
        """
        if not geomType:
            geomType = layer.geometryType()
        if e.button() == Qt.LeftButton:
            # line added to make sure the action is associated with current loop value,
            # lambda function is used with standard parameter set to current loops value.
            # triggeredAction = lambda t=[layer, feature] : self.setSelectionFeature(t[0], feature=t[1], selectAll=selectAll, setActiveLayer=True)
            triggeredAction = partial(self.setSelectionFeature,
                                      layer=layer,
                                      feature=feature,
                                      selectAll=selectAll,
                                      setActiveLayer=True)
            hoveredAction = partial(self.createRubberBand,
                                    feature=feature,
                                    layer=layer,
                                    geom=geomType)
        elif e.button() == Qt.RightButton:
            selected = (QApplication.keyboardModifiers() == Qt.ControlModifier)
            if selected:
                triggeredAction = partial(self.iface.setActiveLayer, layer)
                hoveredAction = None
            else:
                triggeredAction = partial(self.iface.openFeatureForm,
                                          layer,
                                          feature,
                                          showModal=False)
                hoveredAction = partial(self.createRubberBand,
                                        feature=feature,
                                        layer=layer,
                                        geom=geomType)
        return triggeredAction, hoveredAction

    def getCallbackMultipleFeatures(self, e, dictLayerFeature, selectAll=True):
        """
        Sets the callback of an action with a list features as target.
        :param e: (QMouseEvent) mouse event on canvas.
        :param dictLayerFeature: (dict) dictionary containing layers/features to be treated.
        :return: (tuple-of function_lambda) callbacks for triggered and hovered signals.
        """
        # setting the action for the "All" options
        if e.button() == Qt.LeftButton:
            triggeredAction = partial(self.setSelectionListFeature,
                                      dictLayerFeature=dictLayerFeature,
                                      selectAll=selectAll)
        else:
            triggeredAction = partial(self.openMultipleFeatureForm,
                                      dictLayerFeature=dictLayerFeature)
        # to trigger "Hover" signal on QMenu for the multiple options
        hoveredAction = partial(self.createMultipleRubberBand,
                                dictLayerFeature=dictLayerFeature)
        return triggeredAction, hoveredAction

    def createSubmenu(self, e, parentMenu, menuDict, genericAction, selectAll):
        """
        Creates a submenu in a given parent context menu and populates it, with classes/feature sublevels from the menuDict. 
        :param e: (QMouseEvent) mouse event on canvas. If menuDict has only 1 class in it, method will populate parent QMenu.
        :param parentMenu: (QMenu) menu containing the populated submenu
        :param menuDict: (dict) dictionary containing all classes and their features to be filled into submenu.
        :param genericAction: (str) text to be shown into generic action description on the outter level of submenu.
        :return: (dict) mapping of classes and their own QMenu object.
        """
        # creating a dict to handle all "menu" for each class
        submenuDict = dict()
        # sort the layers from diciotnary
        classNameDict = {cl.name(): cl for cl in menuDict}
        layers = sorted(list(classNameDict.keys()))
        for className in layers:
            # menu for features of each class
            cl = classNameDict[className]
            geomType = cl.geometryType()
            # get layer database name
            dsUri = cl.dataProvider().dataSourceUri()
            temp = []
            if '/' in dsUri or '\\' in dsUri:
                db_name = dsUri.split("|")[0] if "|" in dsUri else dsUri
                db_name = os.path.basename(
                    db_name.split("'")[1] if "'" in db_name else db_name)
            elif 'memory' in dsUri:
                db_name = self.tr('{0} (Memory Layer)').format(className)
            else:
                db_name = dsUri.split("'")[1]
            if len(menuDict) == 1:
                # if dictionaty has only 1 class, no need for an extra QMenu - features will be enlisted directly
                # order features by ID to be displayer ordered
                featDict = {feat.id(): feat for feat in menuDict[cl]}
                orderedFeatIdList = sorted(list(featDict.keys()))
                for featId in orderedFeatIdList:
                    feat = featDict[featId]
                    s = "{db_name} | {className} (feat_id = {featId})".format(
                        db_name=db_name, className=className, featId=featId)
                    # inserting action for each feature
                    action = parentMenu.addAction(s)
                    triggeredAction, hoveredAction = self.getCallback(
                        e=e,
                        layer=cl,
                        feature=feat,
                        geomType=geomType,
                        selectAll=selectAll)
                    self.addCallBackToAction(action=action,
                                             onTriggeredAction=triggeredAction,
                                             onHoveredAction=hoveredAction)
                # inserting generic action, if necessary
                if len(menuDict[cl]) > 1:
                    # if there are more than 1 feature to be filled, "All"-command should be added
                    action = parentMenu.addAction(
                        self.tr("{0} From Class {1}").format(
                            genericAction, className))
                    triggeredAction, hoveredAction = self.getCallbackMultipleFeatures(
                        e=e, dictLayerFeature=menuDict, selectAll=selectAll)
                    self.addCallBackToAction(action=action,
                                             onTriggeredAction=triggeredAction,
                                             onHoveredAction=hoveredAction)
                # there is no mapping of class to be exposed, only information added to parent QMenu itself
                return dict()
            title = "{db_name} | {className}".format(db_name=db_name,
                                                     className=className)
            submenuDict[cl] = QMenu(title=title, parent=parentMenu)
            parentMenu.addMenu(submenuDict[cl])
            # inserting an entry for every feature of each class in its own context menu
            # order features by ID to be displayer ordered
            featDict = {feat.id(): feat for feat in menuDict[cl]}
            orderedFeatIdList = sorted(list(featDict.keys()))
            for featId in orderedFeatIdList:
                feat = featDict[featId]
                s = 'feat_id = {0}'.format(featId)
                action = submenuDict[cl].addAction(s)
                triggeredAction, hoveredAction = self.getCallback(
                    e=e,
                    layer=cl,
                    feature=feat,
                    geomType=geomType,
                    selectAll=selectAll)
                self.addCallBackToAction(action=action,
                                         onTriggeredAction=triggeredAction,
                                         onHoveredAction=hoveredAction)
                # set up list for the "All"-commands
                temp.append([cl, feat, geomType])
            # adding generic action for each class
            if len(menuDict[cl]) > 1:
                # if there are more than 1 feature to be filled, "All"-command should be added
                action = submenuDict[cl].addAction(
                    self.tr("{0} From Class {1}").format(
                        genericAction, className))
                triggeredAction, hoveredAction = self.getCallbackMultipleFeatures(
                    e=e,
                    dictLayerFeature={cl: menuDict[cl]},
                    selectAll=selectAll)
                self.addCallBackToAction(action=action,
                                         onTriggeredAction=triggeredAction,
                                         onHoveredAction=hoveredAction)
        return submenuDict

    def setContextMenuStyle(self, e, dictMenuSelected, dictMenuNotSelected):
        """
        Defines how many "submenus" the context menu should have.
        There are 3 context menu scenarios to be handled:
        :param e: (QMouseEvent) mouse event on canvas.
        :param dictMenuSelected: (dict) dictionary of classes and its selected features being treatead.
        :param dictMenuNotSelected: (dict) dictionary of classes and its non selected features being treatead.
        """
        # finding out filling conditions
        selectedDict = bool(dictMenuSelected)
        notSelectedDict = bool(dictMenuNotSelected)
        # finding out if one of either dictionaty are filled ("Exclusive or")
        selectedXORnotSelected = (selectedDict != notSelectedDict)
        # setting "All"-command name
        if e.button() == Qt.RightButton:
            genericAction = self.tr('Open All Feature Forms')
        else:
            genericAction = self.tr('Select All Features')
        # in case one of given dict is empty
        if selectedXORnotSelected:
            if selectedDict:
                menuDict, menu = dictMenuSelected, QMenu(
                    title=self.tr('Selected Features'))
                genericAction = self.tr('Deselect All Features')
                # if the dictionary is from selected features, we want commands to be able to deselect them
                selectAll = False
            else:
                menuDict, menu = dictMenuNotSelected, QMenu(
                    title=self.tr('Not Selected Features'))
                genericAction = self.tr('Select All Features')
                # if the dictionary is from non-selected features, we want commands to be able to select them
                selectAll = True
            if e.button() == Qt.RightButton:
                genericAction = self.tr('Open All Feature Forms')
            self.createSubmenu(e=e,
                               parentMenu=menu,
                               menuDict=menuDict,
                               genericAction=genericAction,
                               selectAll=selectAll)
            if len(menuDict) != 1 and len(list(menuDict.values())) > 1:
                # if there's only one class, "All"-command is given by createSubmenu method
                action = menu.addAction(genericAction)
                triggeredAction, hoveredAction = self.getCallbackMultipleFeatures(
                    e=e, dictLayerFeature=menuDict, selectAll=selectAll)
                self.addCallBackToAction(action=action,
                                         onTriggeredAction=triggeredAction,
                                         onHoveredAction=hoveredAction)
        elif selectedDict:
            # if both of them is empty one more QMenu level is added
            menu = QMenu()
            selectedMenu = QMenu(title=self.tr('Selected Features'))
            notSelectedMenu = QMenu(title=self.tr('Not Selected Features'))
            menu.addMenu(selectedMenu)
            menu.addMenu(notSelectedMenu)
            selectedGenericAction = self.tr('Deselect All Features')
            notSelectedGenericAction = self.tr('Select All Features')
            # selectAll is set to True as now we want command to Deselect Features in case they are selected
            self.createSubmenu(e=e,
                               parentMenu=selectedMenu,
                               menuDict=dictMenuSelected,
                               genericAction=selectedGenericAction,
                               selectAll=False)
            if len(dictMenuSelected) != 1 and len(
                    list(dictMenuSelected.values())) > 1:
                # if there's only one class, "All"-command is given by createSubmenu method
                action = selectedMenu.addAction(selectedGenericAction)
                triggeredAction, hoveredAction = self.getCallbackMultipleFeatures(
                    e=e, dictLayerFeature=dictMenuSelected, selectAll=False)
                self.addCallBackToAction(action=action,
                                         onTriggeredAction=triggeredAction,
                                         onHoveredAction=hoveredAction)
            self.createSubmenu(e=e,
                               parentMenu=notSelectedMenu,
                               menuDict=dictMenuNotSelected,
                               genericAction=notSelectedGenericAction,
                               selectAll=True)
            if len(dictMenuNotSelected) != 1 and len(
                    list(dictMenuNotSelected.values())) > 1:
                # if there's only one class, "All"-command is given by createSubmenu method
                action = notSelectedMenu.addAction(notSelectedGenericAction)
                triggeredAction, hoveredAction = self.getCallbackMultipleFeatures(
                    e=e, dictLayerFeature=dictMenuNotSelected, selectAll=True)
                self.addCallBackToAction(action=action,
                                         onTriggeredAction=triggeredAction,
                                         onHoveredAction=hoveredAction)
        menu.exec_(self.canvas.viewport().mapToGlobal(e.pos()))

    def checkSelectedFeaturesOnDict(self, menuDict):
        """
        Checks all selected features from a given dictionary ( { (QgsVectorLayer)layer : [ (QgsFeature)feat ] } ).
        :param menuDict: (dict) dictionary with layers and their features to be analyzed.
        :return: (tuple-of-dict) both dictionaries of selected and non-selected features of each layer.
        """
        selectedFeaturesDict, notSelectedFeaturesDict = dict(), dict()
        for cl, features in menuDict.items():
            selectedFeats = [f.id() for f in cl.selectedFeatures()]
            for feat in features:
                if feat.id() in selectedFeats:
                    if cl not in selectedFeaturesDict:
                        selectedFeaturesDict[cl] = [feat]
                    else:
                        selectedFeaturesDict[cl].append(feat)
                else:
                    if cl not in notSelectedFeaturesDict:
                        notSelectedFeaturesDict[cl] = [feat]
                    else:
                        notSelectedFeaturesDict[cl].append(feat)
        return selectedFeaturesDict, notSelectedFeaturesDict

    def createContextMenu(self, e):
        """
        Creates the context menu for overlapping layers.
        :param e: mouse event caught from canvas.
        """
        selected = (QApplication.keyboardModifiers() == Qt.ControlModifier)
        if selected:
            firstGeom = self.checkSelectedLayers()
        # setting a list of features to iterate over
        layerList = self.getPrimitiveDict(e, hasControlModifier=selected)
        layers = []
        for key in layerList:
            layers += layerList[key]
        if layers:
            rect = self.getCursorRect(e)
            lyrFeatDict = dict()
            for layer in layers:
                if not isinstance(layer, QgsVectorLayer):
                    continue
                geomType = layer.geometryType()
                # iterate over features inside the mouse bounding box
                bbRect = self.canvas.mapSettings().mapToLayerCoordinates(
                    layer, rect)
                for feature in layer.getFeatures(QgsFeatureRequest(bbRect)):
                    geom = feature.geometry()
                    if geom:
                        searchRect = self.geometryHandler.reprojectSearchArea(
                            layer, rect)
                        if selected:
                            # if Control was held, appending behaviour is different
                            if not firstGeom:
                                firstGeom = geomType
                            elif firstGeom > geomType:
                                firstGeom = geomType
                            if geomType == firstGeom and geom.intersects(
                                    searchRect):
                                # only appends features if it has the same geometry as first selected feature
                                if layer in lyrFeatDict:
                                    lyrFeatDict[layer].append(feature)
                                else:
                                    lyrFeatDict[layer] = [feature]
                        else:
                            if geom.intersects(searchRect):
                                if layer in lyrFeatDict:
                                    lyrFeatDict[layer].append(feature)
                                else:
                                    lyrFeatDict[layer] = [feature]
            lyrFeatDict = self.filterStrongestGeometry(lyrFeatDict)
            if lyrFeatDict:
                moreThanOneFeat = len(list(lyrFeatDict.values())) > 1 or len(
                    list(lyrFeatDict.values())[0]) > 1
                if moreThanOneFeat:
                    # if there are overlapping features (valid candidates only)
                    selectedFeaturesDict, notSelectedFeaturesDict = self.checkSelectedFeaturesOnDict(
                        menuDict=lyrFeatDict)
                    self.setContextMenuStyle(
                        e=e,
                        dictMenuSelected=selectedFeaturesDict,
                        dictMenuNotSelected=notSelectedFeaturesDict)
                else:
                    layer = list(lyrFeatDict.keys())[0]
                    feature = lyrFeatDict[layer][0]
                    selected = (
                        QApplication.keyboardModifiers() == Qt.ControlModifier)
                    if e.button() == Qt.LeftButton:
                        # if feature is selected, we want it to be de-selected
                        self.setSelectionFeature(layer=layer,
                                                 feature=feature,
                                                 selectAll=False,
                                                 setActiveLayer=True)
                    elif selected:
                        self.iface.setActiveLayer(layer)
                    else:
                        self.iface.openFeatureForm(layer,
                                                   feature,
                                                   showModal=False)

    def unload(self):
        self.deactivate()
Ejemplo n.º 13
0
class DsgRasterInfoTool(QWidget, Ui_DsgRasterInfoTool):
    """
    This class is supposed to help revision operators. It shows, on mouse hovering
    raster layer's band values. For a MDS product, altimetry is, then, given.
    Tool Behaviour:
    1- On hoverring a pixel: expose band value(s)
    2- On mouse click: create a new instance of desired layer (filled on config).
        * behaviour 2 is an extrapolation of first conception
    """
    def __init__(self, iface, parent=None):
        """
        Class constructor.
        """
        self.canvas = iface.mapCanvas()
        super(DsgRasterInfoTool, self).__init__(parent)
        self.setupUi(self)
        self.bandTooltipButton.setToolTip(self.tr("Show raster tooltip"))
        self.dynamicHistogramButton.setToolTip(
            self.tr("Dynamic histogram view"))
        self.valueSetterButton.setToolTip(
            self.
            tr("Set raster value from mouse click\nShift + Left Click + Mouse Drag: Selects a set of points and assigns raster value for each point"
               ))
        self.assignBandValueTool = None
        self.parent = parent
        self.splitter.hide()
        self.iface = iface
        self.timerMapTips = QTimer(self.canvas)
        self.geometryHandler = GeometryHandler(iface)
        self.addShortcuts()
        self.valueSetterButton.setEnabled(False)
        self.iface.mapCanvas().currentLayerChanged.connect(
            self.enableAssignValue)
        self.iface.actionToggleEditing().triggered.connect(
            self.enableAssignValue)
        self.iface.mapCanvas().mapToolSet.connect(self.enableAssignValue)
        self.valueSetterButton.toggled.connect(self.activateValueSetter)
        # self.rasterComboBox.currentIndexChanged.connect(self.enableAssignValue)
        # start currentLayer selection
        self.currentLayer = None

    def resetEditingSignals(self, currentLayer):
        """
        Disconnects editing signal from previously selected layer and connects it to newly selected layer.
        Method is called whenever currentlLayerChanged signal is emitted.
        """
        # get previous selected layer
        prevLayer = self.currentLayer
        # update current selected layer
        if not currentLayer:
            self.currentLayer = currentLayer
        self.activateAlias = partial(self.activateValueSetter, True)
        self.deactivateAlias = partial(self.activateValueSetter, False)
        if prevLayer:
            try:
                # if there was a previous selection, signals must be disconnected from it before connecting to the new layer
                prevLayer.editingStarted.disconnect(self.activateAlias)
                prevLayer.editingStopped.disconnect(self.deactivateAlias)
            except:
                # in case signal is not yet connected, somehow
                pass
        # connecting signals to new layer
        if isinstance(self.currentLayer, QgsVectorLayer):
            if self.currentLayer.geometryType() == QgsWkbTypes.PointGeometry:
                self.currentLayer.editingStarted.connect(self.activateAlias)
                self.currentLayer.editingStopped.connect(self.deactivateAlias)

    def add_action(self, icon_path, text, callback, parent=None):
        icon = QIcon(icon_path)
        action = QAction(icon, text, parent)
        action.triggered.connect(callback)
        if parent:
            parent.addAction(action)
        return action

    def addShortcuts(self):
        icon_path = ':/plugins/DsgTools/icons/rasterToolTip.png'
        text = self.tr('DSGTools: Raster information tool')
        self.activateToolAction = self.add_action(
            icon_path,
            text,
            self.rasterInfoPushButton.toggle,
            parent=self.parent)
        self.iface.registerMainWindowAction(self.activateToolAction, '')
        icon_path = ':/plugins/DsgTools/icons/band_tooltip.png'
        text = self.tr('DSGTools: Band tooltip')
        self.bandTooltipButtonAction = self.add_action(
            icon_path, text, self.bandTooltipButton.toggle, parent=self.parent)
        self.iface.registerMainWindowAction(self.bandTooltipButtonAction, '')
        icon_path = ':/plugins/DsgTools/icons/dynamic_histogram_viewer.png'
        text = self.tr('DSGTools: Dynamic Histogram Viewer')
        self.dynamicHistogramButtonAction = self.add_action(
            icon_path,
            text,
            self.dynamicHistogramButton.toggle,
            parent=self.parent)
        self.iface.registerMainWindowAction(self.dynamicHistogramButtonAction,
                                            '')
        icon_path = ':/plugins/DsgTools/icons/valueSetter.png'
        text = self.tr('DSGTools: Set Value From Point')
        self.valueSetterButtonAction = self.add_action(
            icon_path, text, self.valueSetterButton.toggle, parent=self.parent)
        self.iface.registerMainWindowAction(self.valueSetterButtonAction, '')
        # self.timerMapTips.timeout.connect( self.showToolTip )

    def disconnectAllSignals(self):
        """
        Disconnects all signals connected/related to Set Value Checker tool.
        """
        try:
            self.valueSetterButton.toggled.disconnect(self.activateValueSetter)
            # self.valueSetterButton.blockSignals(True)
        except:
            pass
        # try:
        #     self.rasterComboBox.currentIndexChanged.disconnect(self.enableAssignValue)
        # except:
        #     pass
        try:
            self.iface.mapCanvas().currentLayerChanged.disconnect(
                self.enableAssignValue)
        except:
            pass
        try:
            self.iface.actionToggleEditing().triggered.disconnect(
                self.enableAssignValue)
        except:
            pass
        try:
            self.iface.mapCanvas().mapToolSet.disconnect(
                self.enableAssignValue)
        except:
            pass
        try:
            self.currentLayer.editingStarted.disconnect(self.activateAlias)
        except:
            pass
        try:
            self.currentLayer.editingStopped.disconnect(self.deactivateAlias)
        except:
            pass

    def connectAllSignals(self):
        """
        Connects all signals connected/related to Set Value Checker tool.
        """
        self.valueSetterButton.toggled.connect(self.activateValueSetter)
        # self.valueSetterButton.blockSignals(False)
        self.iface.mapCanvas().currentLayerChanged.connect(
            self.enableAssignValue)
        self.iface.actionToggleEditing().triggered.connect(
            self.enableAssignValue)
        self.iface.mapCanvas().mapToolSet.connect(self.enableAssignValue)
        # self.rasterComboBox.currentIndexChanged.connect(self.enableAssignValue)
        if self.currentLayer:
            self.currentLayer.editingStarted.connect(self.activateAlias)
            self.currentLayer.editingStopped.connect(self.deactivateAlias)

    def enableAssignValue(self, newTool=None, oldTool=None):
        self.disconnectAllSignals()
        layer = self.iface.mapCanvas().currentLayer()
        if layer and isinstance(layer, QgsVectorLayer):
            if layer.geometryType(
            ) == QgsWkbTypes.PointGeometry and layer.isEditable(
            ) and not self.rasterComboBox.currentLayer() is None:
                self.valueSetterButton.setEnabled(True)
                # reset editing signals
                self.resetEditingSignals(currentLayer=layer)
            else:
                self.valueSetterButton.setEnabled(False)
                if self.valueSetterButton.isChecked():
                    self.valueSetterButton.setChecked(False)
                    self.activateValueSetter(False)
        else:
            self.valueSetterButton.setEnabled(False)
            if self.valueSetterButton.isChecked():
                self.valueSetterButton.setChecked(False)
                self.activateValueSetter(False)
        self.connectAllSignals()

    def deactivate(self):
        self.activateBandValueTool(False)
        self.activateStretchTool(False)
        self.activateValueSetter(False)

    @pyqtSlot(bool, name='on_rasterInfoPushButton_toggled')
    def toggleBar(self, toggled=None):
        """
        Shows/Hides the tool bar
        """
        if toggled is None:
            toggled = self.rasterInfoPushButton.isChecked()
        if toggled:
            self.splitter.show()
        else:
            self.splitter.hide()

    @pyqtSlot(bool, name='on_bandTooltipButton_toggled')
    def activateBandValueTool(self, state):
        if state:
            self.iface.mapCanvas().xyCoordinates.connect(self.showToolTip)
        else:
            self.iface.mapCanvas().xyCoordinates.disconnect(self.showToolTip)

    @pyqtSlot(bool, name='on_dynamicHistogramButton_toggled')
    def activateStretchTool(self, state):
        if state:
            self.iface.mapCanvas().extentsChanged.connect(self.stretch_raster)
        else:
            self.iface.mapCanvas().extentsChanged.disconnect(
                self.stretch_raster)

    def stretch_raster(self):
        try:
            formerLayer = self.iface.activeLayer()
            layer = self.rasterComboBox.currentLayer()
            # keep track of current tool status
            assignValueStatus = self.valueSetterButton.isChecked()
            self.iface.setActiveLayer(layer)
            self.iface.mainWindow().findChild(
                QAction, 'mActionLocalCumulativeCutStretch').trigger()
            self.iface.setActiveLayer(formerLayer)
            # make sure it still be on, if necessary
            if assignValueStatus:
                self.valueSetterButton.setChecked(assignValueStatus)
        except AttributeError:
            pass

    # @pyqtSlot(bool, name = 'on_valueSetterButton_toggled')
    def activateValueSetter(self, state):
        if state:
            raster = self.rasterComboBox.currentLayer()
            self.loadTool(self.iface, raster)
        else:
            self.unloadTool()

    def loadTool(self, iface, raster):
        self.disconnectAllSignals()
        self.assignBandValueTool = AssignBandValueTool(self.iface, raster)
        self.assignBandValueTool.activate()
        self.iface.mapCanvas().setMapTool(self.assignBandValueTool)
        self.connectAllSignals()

    def unloadTool(self):
        self.disconnectAllSignals()
        if self.assignBandValueTool:
            self.assignBandValueTool.deactivate()
            self.iface.mapCanvas().unsetMapTool(self.assignBandValueTool)
        self.assignBandValueTool = None
        self.iface.mapCanvas().mapToolSet.connect(self.enableAssignValue)
        self.connectAllSignals()

    def getPixelValue(self, mousePos, rasterLayer):
        """
        
        """
        rasterCrs = rasterLayer.crs()
        mousePosGeom = QgsGeometry.fromPointXY(mousePos)
        canvasCrs = self.canvas.mapSettings().destinationCrs()
        self.geometryHandler.reprojectFeature(mousePosGeom, rasterCrs,
                                              canvasCrs)
        mousePos = mousePosGeom.asPoint()
        # identify pixel(s) information
        i = rasterLayer.dataProvider().identify(mousePos,
                                                QgsRaster.IdentifyFormatValue)
        if i.isValid():
            text = ", ".join([
                '{0:g}'.format(r) for r in list(i.results().values())
                if r is not None
            ])
        else:
            text = ""
        return text

    def showToolTip(self, qgsPoint):
        """
        
        """
        self.timerMapTips.stop()
        self.timerMapTips.start(6000)  # time in milliseconds
        if self.canvas.underMouse():
            raster = self.rasterComboBox.currentLayer()
            if raster:
                text = self.getPixelValue(qgsPoint, raster)
                p = self.canvas.mapToGlobal(self.canvas.mouseLastXY())
                QToolTip.showText(p, text, self.canvas)

    def unload(self):
        self.disconnectAllSignals()
        try:
            self.iface.mapCanvas().extentsChanged.disconnect(
                self.stretch_raster)
        except:
            pass
        try:
            self.iface.mapCanvas().xyCoordinates.disconnect(self.showToolTip)
        except:
            pass
        self.iface.unregisterMainWindowAction(self.activateToolAction)
        self.iface.unregisterMainWindowAction(self.valueSetterButtonAction)
        self.iface.unregisterMainWindowAction(self.bandTooltipButtonAction)
        self.iface.unregisterMainWindowAction(
            self.dynamicHistogramButtonAction)