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)
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}
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 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'))
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}
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}
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}
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()
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()
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)