def __init__(self, iface, parent=None): """ Class constructor. """ # super(QgsRasterLayer, self).__init__() 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.DsgGeometryHandler = DsgGeometryHandler(iface) self.addShortcuts() self.valueSetterButton.setEnabled(False) self.iface.mapCanvas().currentLayerChanged.connect( self.enableAssignValue) self.iface.actionToggleEditing().triggered.connect( self.enableAssignValue) # start currentLayer selection self.currentLayer = None
def __init__(self, postgisDb, iface, instantiating=False): """ Constructor """ super(IdentifyOutOfBoundsAnglesProcess, self).__init__(postgisDb, iface, instantiating) self.processAlias = self.tr('Identify Out Of Bounds Angles') self.geometryHandler = DsgGeometryHandler(iface, parent=iface.mapCanvas()) if not self.instantiating: # getting tables with elements self.classesWithElemDict = self.abstractDb.getGeomColumnDictV2( primitiveFilter=['a', 'l'], withElements=True, excludeValidation=True) # adjusting process parameters interfaceDictList = [] for key in self.classesWithElemDict: cat, lyrName, geom, geomType, tableType = key.split(',') interfaceDictList.append({ self.tr('Category'): cat, self.tr('Layer Name'): lyrName, self.tr('Geometry\nColumn'): geom, self.tr('Geometry\nType'): geomType, self.tr('Layer\nType'): tableType }) self.parameters = { 'Angle': 10.0, 'Classes': interfaceDictList, 'Only Selected': False }
def __init__(self, canvas, iface): self.iface = iface self.canvas = canvas self.toolAction = None QgsMapTool.__init__(self, self.canvas) self.DsgGeometryHandler = DsgGeometryHandler(iface) self.iface.currentLayerChanged.connect(self.setToolEnabled) self.iface.actionToggleEditing().triggered.connect(self.setToolEnabled)
def __init__(self, iface, parent): """ Class constructor. """ # super(QgsRasterLayer, self).__init__() self.canvas = iface.mapCanvas() super(BandValueTool, self).__init__(self.canvas) self.parent = parent self.iface = iface self.toolAction = None self.QgsMapToolEmitPoint = QgsMapToolEmitPoint(self.canvas) self.DsgGeometryHandler = DsgGeometryHandler(iface) self.timerMapTips = QTimer( self.canvas ) self.timerMapTips.timeout.connect( self.showToolTip ) self.activated = False
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.dsgGeometryHandler = DsgGeometryHandler(iface) self.rasterLayer = rasterLayer self.setRubberbandParameters() self.reset() self.auxList = [] self.decimals = self.getDecimals()
def __init__(self, iface, parent=None): """ Class constructor. """ # super(QgsRasterLayer, self).__init__() self.canvas = iface.mapCanvas() super(DsgRasterInfoTool, self).__init__(parent) self.setupUi(self) self.assignBandValueTool = None self.parent = parent self.splitter.hide() self.iface = iface self.timerMapTips = QTimer(self.canvas) self.DsgGeometryHandler = DsgGeometryHandler(iface) self.addShortcuts() self.valueSetterButton.setEnabled(False) self.iface.mapCanvas().currentLayerChanged.connect( self.enableAssignValue) self.iface.actionToggleEditing().triggered.connect( self.enableAssignValue)
def __init__(self, iface, parent = None): """ Class constructor. """ # super(QgsRasterLayer, self).__init__() 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.DsgGeometryHandler = DsgGeometryHandler(iface) self.addShortcuts() self.valueSetterButton.setEnabled(False) self.iface.mapCanvas().currentLayerChanged.connect(self.enableAssignValue) self.iface.actionToggleEditing().triggered.connect(self.enableAssignValue) # start currentLayer selection self.currentLayer = None
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. """ # super(QgsRasterLayer, self).__init__() 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.DsgGeometryHandler = DsgGeometryHandler(iface) self.addShortcuts() self.valueSetterButton.setEnabled(False) self.iface.mapCanvas().currentLayerChanged.connect(self.enableAssignValue) self.iface.actionToggleEditing().triggered.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 self.currentLayer = currentLayer activateAlias = lambda : self.activateValueSetter(True) deactivateAlias = lambda : 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(activateAlias) prevLayer.editingStopped.disconnect(deactivateAlias) except: # in case signal is not yet connected, somehow pass # connecting signals to new layer if isinstance(self.currentLayer, QgsVectorLayer): self.currentLayer.editingStarted.connect(activateAlias) self.currentLayer.editingStopped.connect(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/histogram.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 enableAssignValue(self): layer = self.iface.mapCanvas().currentLayer() if layer and isinstance(layer, QgsVectorLayer): if layer.geometryType() == QGis.Point and layer.isEditable(): self.valueSetterButton.setEnabled(True) # reset editing signals self.resetEditingSignals(currentLayer=layer) else: self.valueSetterButton.setEnabled(False) self.valueSetterButton.setChecked(False) self.activateValueSetter(False) else: self.valueSetterButton.setEnabled(False) self.valueSetterButton.setChecked(False) self.activateValueSetter(False) 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) @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.assignBandValueTool = AssignBandValueTool(self.iface, raster) self.assignBandValueTool.activate() def unloadTool(self): if self.assignBandValueTool: self.assignBandValueTool.deactivate() self.assignBandValueTool = None def stretch_raster(self): try: formerLayer = self.iface.activeLayer() layer = self.rasterComboBox.currentLayer() self.iface.mapCanvas().currentLayerChanged.disconnect(self.enableAssignValue) self.iface.setActiveLayer(layer) self.iface.mainWindow().findChild( QAction, 'mActionLocalCumulativeCutStretch' ).trigger() self.iface.setActiveLayer(formerLayer) self.iface.mapCanvas().currentLayerChanged.connect(self.enableAssignValue) except AttributeError: pass def getPixelValue(self, mousePos, rasterLayer): """ """ rasterCrs = rasterLayer.crs() mousePosGeom = QgsGeometry.fromPoint(mousePos) canvasCrs = self.canvas.mapRenderer().destinationCrs() self.DsgGeometryHandler.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 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 )
class IdentifyOutOfBoundsAnglesProcess(ValidationProcess): def __init__(self, postgisDb, iface, instantiating=False): """ Constructor """ super(IdentifyOutOfBoundsAnglesProcess,self).__init__(postgisDb, iface, instantiating) self.processAlias = self.tr('Identify Out Of Bounds Angles') self.geometryHandler = DsgGeometryHandler(iface, parent = iface.mapCanvas()) if not self.instantiating: # getting tables with elements self.classesWithElemDict = self.abstractDb.getGeomColumnDictV2(primitiveFilter=['a', 'l'], withElements=True, excludeValidation = True) # adjusting process parameters interfaceDictList = [] for key in self.classesWithElemDict: cat, lyrName, geom, geomType, tableType = key.split(',') interfaceDictList.append({self.tr('Category'):cat, self.tr('Layer Name'):lyrName, self.tr('Geometry\nColumn'):geom, self.tr('Geometry\nType'):geomType, self.tr('Layer\nType'):tableType}) self.parameters = {'Angle': 10.0, 'Classes': interfaceDictList, 'Only Selected':False} def getOutOfBoundsAngleInPolygon(self, feat, geometry_column, part, angle, outOfBoundsList): for linearRing in part.asPolygon(): linearRing = self.geometryHandler.getClockWiseList(linearRing) nVertex = len(linearRing)-1 for i in xrange(nVertex): if i == 0: vertexAngle = (linearRing[i].azimuth(linearRing[-2]) - linearRing[i].azimuth(linearRing[i+1]) + 360) else: vertexAngle = (linearRing[i].azimuth(linearRing[i-1]) - linearRing[i].azimuth(linearRing[i+1]) + 360) vertexAngle = math.fmod(vertexAngle, 360) if vertexAngle > 180: # if angle calculated is the outter one vertexAngle = 360 - vertexAngle if vertexAngle < angle: geomDict = {'angle':vertexAngle,'feat_id':feat.id(), 'geometry_column': geometry_column, 'geom':QgsGeometry.fromPoint(linearRing[i])} outOfBoundsList.append(geomDict) def getOutOfBoundsAngleInLine(self, feat, geometry_column, part, angle, outOfBoundsList): line = part.asPolyline() nVertex = len(line)-1 for i in xrange(1,nVertex): vertexAngle = (line[i].azimuth(line[i-1]) - line[i].azimuth(line[i+1]) + 360) vertexAngle = math.fmod(vertexAngle, 360) if vertexAngle > 180: vertexAngle = 360 - vertexAngle if vertexAngle < angle: geomDict = {'angle':vertexAngle,'feat_id':feat.id(), 'geometry_column': geometry_column, 'geom':QgsGeometry.fromPoint(line[i])} outOfBoundsList.append(geomDict) def getOutOfBoundsAngle(self, feat, angle, geometry_column): outOfBoundsList = [] geom = feat.geometry() for part in geom.asGeometryCollection(): if part.type() == QGis.Polygon: self.getOutOfBoundsAngleInPolygon(feat, geometry_column, part, angle, outOfBoundsList) if part.type() == QGis.Line: self.getOutOfBoundsAngleInLine(feat, geometry_column, part, angle, outOfBoundsList) return outOfBoundsList def getOutOfBoundsAngleList(self, lyr, angle, geometry_column, onlySelected = False): featureList, size = self.getFeatures(lyr, onlySelected = onlySelected) outOfBoundsList = [] for feat in featureList: outOfBoundsList += self.getOutOfBoundsAngle(feat, angle, geometry_column) return outOfBoundsList def buildAndRaiseOutOfBoundsFlag(self, tableSchema, tableName, flagLyr, geomDictList): """ """ featFlagList = [] for geomDict in geomDictList: # reason = self.tr('Angle of {0} degrees is out of bound.').format(geomDict['angle']) reason = self.tr('Angle out of bounds ({0:.2f} deg)').format(geomDict['angle']) newFlag = self.buildFlagFeature(flagLyr, self.processName, tableSchema, tableName, geomDict['feat_id'], geomDict['geometry_column'], geomDict['geom'], reason) featFlagList.append(newFlag) return self.raiseVectorFlags(flagLyr, featFlagList) def execute(self): """ Reimplementation of the execute method from the parent class """ QgsMessageLog.logMessage(self.tr('Starting ')+self.getName()+self.tr(' Process.'), "DSG Tools Plugin", QgsMessageLog.CRITICAL) try: self.setStatus(self.tr('Running'), 3) #now I'm running! self.abstractDb.deleteProcessFlags(self.getName()) #erase previous flags classesWithElem = self.parameters['Classes'] if len(classesWithElem) == 0: self.setStatus(self.tr('No classes selected!. Nothing to be done.'), 1) #Finished QgsMessageLog.logMessage(self.tr('No classes selected! Nothing to be done.'), "DSG Tools Plugin", QgsMessageLog.CRITICAL) return 1 tol = self.parameters['Angle'] error = False flagLyr = self.getFlagLyr(0) for key in classesWithElem: self.startTimeCount() # preparation classAndGeom = self.classesWithElemDict[key] lyr = self.loadLayerBeforeValidationProcess(classAndGeom) # running the process localProgress = ProgressWidget(0, 1, self.tr('Running process on ')+classAndGeom['tableName'], parent=self.iface.mapCanvas()) localProgress.step() result = self.getOutOfBoundsAngleList(lyr, tol, classAndGeom['geom'], onlySelected = self.parameters['Only Selected']) localProgress.step() # storing flags if len(result) > 0: numberOfProblems = self.buildAndRaiseOutOfBoundsFlag(classAndGeom['tableSchema'], classAndGeom['tableName'], flagLyr, result) QgsMessageLog.logMessage(str(numberOfProblems) + self.tr(' features from') + classAndGeom['tableName'] + self.tr(' have out of bounds angle(s). Check flags.'), "DSG Tools Plugin", QgsMessageLog.CRITICAL) else: QgsMessageLog.logMessage(self.tr('There are no out of bounds angles on ') + classAndGeom['tableName'] + '.', "DSG Tools Plugin", QgsMessageLog.CRITICAL) self.logLayerTime(classAndGeom['tableSchema']+'.'+classAndGeom['tableName']) if error: self.setStatus(self.tr('There are features with angles out of bounds. Check log.'), 4) #Finished with errors else: self.setStatus(self.tr('There are no features with angles out of bounds.'), 1) #Finished return 1 except Exception as e: QgsMessageLog.logMessage(':'.join(e.args), "DSG Tools Plugin", QgsMessageLog.CRITICAL) self.finishedWithError() return 0
class FlipLine(QgsMapTool): """ Tool expected behaviour: When (valid) line features are selected, it flips them upon tool activation. """ def __init__(self, canvas, iface): self.iface = iface self.canvas = canvas self.toolAction = None QgsMapTool.__init__(self, self.canvas) self.DsgGeometryHandler = DsgGeometryHandler(iface) self.iface.currentLayerChanged.connect(self.setToolEnabled) self.iface.actionToggleEditing().triggered.connect(self.setToolEnabled) def setToolEnabled(self, layer): try: if isinstance(self.sender(), QtGui.QAction): layer = self.iface.mapCanvas().currentLayer() except: from PyQt4 import QtGui if isinstance(self.sender(), QtGui.QAction): layer = self.iface.mapCanvas().currentLayer() if not layer or not isinstance( layer, QgsVectorLayer ) or layer.geometryType() != QGis.Line or not layer.isEditable(): enabled = False else: enabled = True self.toolAction.setEnabled(enabled) return enabled def activate(self): """ Activates tool. """ if self.toolAction: self.toolAction.setChecked(False) def deactivate(self): self.iface.currentLayerChanged.disconnect(self.setToolEnabled) self.iface.actionToggleEditing().triggered.disconnect( self.setToolEnabled) def setAction(self, action): """ Defines an action for tool. action: QAction to be set. """ self.toolAction = action def canvasReleaseEvent(self, e): """ Selects all line features inside the rectangle created by dragging the mouse around canvas whilst holding Shift key. :param e: mouse event on canvas. """ if QtGui.QApplication.keyboardModifiers() == QtCore.Qt.ShiftModifier: self.isEmittingPoint = False r = self.rectangle() layers = self.canvas.layers() for layer in layers: #ignore layers on black list and features that are not vector layers and if layer not a line if (not isinstance(layer, QgsVectorLayer) ) or layer.geometryType() != 1 or ( self.layerHasPartInBlackList(layer.name())): continue if r is not None: #builds bbRect and select from layer, adding selection bbRect = self.canvas.mapSettings().mapToLayerCoordinates( layer, r) layer.select(bbRect, True) self.rubberBand.hide() def getAllSelectedLines(self): """ Gets all selected lines on canvas. """ selection = [] for layer in self.iface.legendInterface().layers(): if (not isinstance(layer, QgsVectorLayer)) or layer.geometryType() != 1: continue for feat in layer.selectedFeatures(): selection.append([layer, feat, layer.geometryType()]) return selection def flipSelectedLines(self): """ Method for flipping all selected lines. Used for button callback. """ # get all selected features and remove all features that are not lines selectedFeatures = self.getAllSelectedLines() pop = 0 for idx, item in enumerate(selectedFeatures): if item[2] != 1: selectedFeatures.pop(idx - pop) pop += 1 if not selectedFeatures: logMsg = self.getLogMessage(None, None) self.iface.messageBar().pushMessage(self.tr('Error'), logMsg, level=QgsMessageBar.CRITICAL, duration=3) # QtGui.QMessageBox.critical(self, self.tr('Critical!'), logMsg) QgsMessageLog.logMessage(logMsg, "DSG Tools Plugin", QgsMessageLog.CRITICAL) return # call the method for flipping features from geometry module flippedLines, failedLines = self.DsgGeometryHandler.flipFeatureList( featureList=selectedFeatures, debugging=True) logMsg = self.getLogMessage(flippedLines, failedLines) self.iface.messageBar().pushMessage(self.tr('Success'), logMsg, level=QgsMessageBar.INFO, duration=3) QgsMessageLog.logMessage(logMsg, "DSG Tools Plugin", QgsMessageLog.INFO) def getLogMessage(self, flippedLines, failedLines): """ Method for mounting log message to be exposed to user. :param flippedLines: list of lines that were selected and were successfully flipped. :param failedLines: list of lines that were selected and failed to be flipped. :param success: indicates whether the log is for a failed execution or """ nrFlipped = nrFailed = 0 if not flippedLines and not failedLines: return self.tr('There are no (valid) lines selected!') if flippedLines: nrFlipped = len(flippedLines) logMsg = self.tr("Feature(s) flipped: ") for item in flippedLines: logMsg += "{} (id={}), ".format(item[0].name(), item[1].id()) logMsg = (logMsg + ")").replace(", )", "") elif failedLines: nrFailed = len(failedLines) logMsg += self.tr("\nFeature(s) that failed to be flipped: ") for item in failedLines: logMsg += "{} (id={}), ".format(item[0].name(), item[1].id()) logMsg = (logMsg + ")").replace(", )", ".") return logMsg + self.tr("\n{} lines flipped. {} failed to be flipped." ).format(nrFlipped, nrFailed) def startFlipLineTool(self): if self.canvas.currentLayer() in self.iface.editableLayers(): self.flipSelectedLines() else: self.iface.messageBar().pushMessage( self.tr('Warning'), self.tr('Start editing in current layer!'), level=QgsMessageBar.INFO, duration=3)
def __init__(self, postgisDb, iface, instantiating=False): """ Class constructor. :param postgisDb: (DsgTools.AbstractDb) postgis database connection. :param iface: (QgisInterface) QGIS interface object. :param instantiating: (bool) indication of whether method is being instatiated. """ super(CreateNetworkNodesProcess, self).__init__(postgisDb, iface, instantiating) self.processAlias = self.tr('Create Network Nodes') self.hidNodeLayerName = 'aux_hid_nodes_p' self.canvas = self.iface.mapCanvas() self.DsgGeometryHandler = DsgGeometryHandler(iface) # name for node types (check enum atop) self.nodeTypeNameDict = { CreateNetworkNodesProcess.Flag: self.tr("Flag"), CreateNetworkNodesProcess.Sink: self.tr("Sink"), CreateNetworkNodesProcess.WaterwayBegin: self.tr("Waterway Beginning"), CreateNetworkNodesProcess.UpHillNode: self.tr("Up Hill Node"), CreateNetworkNodesProcess.DownHillNode: self.tr("Down Hill Node"), CreateNetworkNodesProcess.Confluence: self.tr("Confluence"), CreateNetworkNodesProcess.Ramification: self.tr("Ramification"), CreateNetworkNodesProcess.AttributeChange: self.tr("Attribute Change Node"), CreateNetworkNodesProcess.NodeNextToWaterBody: self.tr("Node Next to Water Body"), CreateNetworkNodesProcess.AttributeChangeFlag: self.tr("Attribute Change Flag"), CreateNetworkNodesProcess.NodeOverload: self.tr("Overloaded Node"), CreateNetworkNodesProcess.DisconnectedLine: self.tr("Disconnected From Network") } if not self.instantiating: # getting tables with elements (line primitive) self.classesWithElemDict = self.abstractDb.getGeomColumnDictV2( withElements=True, excludeValidation=True) # adjusting process parameters interfaceDict = dict() for key in self.classesWithElemDict: cat, lyrName, geom, geomType, tableType = key.split(',') interfaceDict[key] = { self.tr('Category'): cat, self.tr('Layer Name'): lyrName, self.tr('Geometry\nColumn'): geom, self.tr('Geometry\nType'): geomType, self.tr('Layer\nType'): tableType } self.networkClassesWithElemDict = self.abstractDb.getGeomColumnDictV2( primitiveFilter=['l'], withElements=True, excludeValidation=True) networkFlowParameterList = HidrographyFlowParameters( self.networkClassesWithElemDict.keys()) self.sinkClassesWithElemDict = self.abstractDb.getGeomColumnDictV2( primitiveFilter=['p'], withElements=True, excludeValidation=True) sinkFlowParameterList = HidrographyFlowParameters( self.sinkClassesWithElemDict.keys()) self.parameters = { 'Only Selected': False, 'Network Layer': networkFlowParameterList, 'Sink Layer': sinkFlowParameterList, 'Search Radius': 5.0, 'Reference and Water Body Layers': OrderedDict({ 'referenceDictList': {}, 'layersDictList': interfaceDict }) } self.nodeDict = None self.nodeTypeDict = None
class CreateNetworkNodesProcess(ValidationProcess): # enum for node types Flag, Sink, WaterwayBegin, UpHillNode, DownHillNode, Confluence, Ramification, AttributeChange, NodeNextToWaterBody, AttributeChangeFlag, NodeOverload, DisconnectedLine = range( 12) def __init__(self, postgisDb, iface, instantiating=False): """ Class constructor. :param postgisDb: (DsgTools.AbstractDb) postgis database connection. :param iface: (QgisInterface) QGIS interface object. :param instantiating: (bool) indication of whether method is being instatiated. """ super(CreateNetworkNodesProcess, self).__init__(postgisDb, iface, instantiating) self.processAlias = self.tr('Create Network Nodes') self.hidNodeLayerName = 'aux_hid_nodes_p' self.canvas = self.iface.mapCanvas() self.DsgGeometryHandler = DsgGeometryHandler(iface) # name for node types (check enum atop) self.nodeTypeNameDict = { CreateNetworkNodesProcess.Flag: self.tr("Flag"), CreateNetworkNodesProcess.Sink: self.tr("Sink"), CreateNetworkNodesProcess.WaterwayBegin: self.tr("Waterway Beginning"), CreateNetworkNodesProcess.UpHillNode: self.tr("Up Hill Node"), CreateNetworkNodesProcess.DownHillNode: self.tr("Down Hill Node"), CreateNetworkNodesProcess.Confluence: self.tr("Confluence"), CreateNetworkNodesProcess.Ramification: self.tr("Ramification"), CreateNetworkNodesProcess.AttributeChange: self.tr("Attribute Change Node"), CreateNetworkNodesProcess.NodeNextToWaterBody: self.tr("Node Next to Water Body"), CreateNetworkNodesProcess.AttributeChangeFlag: self.tr("Attribute Change Flag"), CreateNetworkNodesProcess.NodeOverload: self.tr("Overloaded Node"), CreateNetworkNodesProcess.DisconnectedLine: self.tr("Disconnected From Network") } if not self.instantiating: # getting tables with elements (line primitive) self.classesWithElemDict = self.abstractDb.getGeomColumnDictV2( withElements=True, excludeValidation=True) # adjusting process parameters interfaceDict = dict() for key in self.classesWithElemDict: cat, lyrName, geom, geomType, tableType = key.split(',') interfaceDict[key] = { self.tr('Category'): cat, self.tr('Layer Name'): lyrName, self.tr('Geometry\nColumn'): geom, self.tr('Geometry\nType'): geomType, self.tr('Layer\nType'): tableType } self.networkClassesWithElemDict = self.abstractDb.getGeomColumnDictV2( primitiveFilter=['l'], withElements=True, excludeValidation=True) networkFlowParameterList = HidrographyFlowParameters( self.networkClassesWithElemDict.keys()) self.sinkClassesWithElemDict = self.abstractDb.getGeomColumnDictV2( primitiveFilter=['p'], withElements=True, excludeValidation=True) sinkFlowParameterList = HidrographyFlowParameters( self.sinkClassesWithElemDict.keys()) self.parameters = { 'Only Selected': False, 'Network Layer': networkFlowParameterList, 'Sink Layer': sinkFlowParameterList, 'Search Radius': 5.0, 'Reference and Water Body Layers': OrderedDict({ 'referenceDictList': {}, 'layersDictList': interfaceDict }) } self.nodeDict = None self.nodeTypeDict = None def getFrameOutterBounds(self, frameLayer): """ Gets the outter bounds of all frame features composing frame layer. :param frameLayer: (QgsVectorLayer) frame layer. :return: (list-of-QgsGeometry) list of all disjuncts outter bounds of features in frame layer. """ frameGeomList = [] # dissolve every feature into a single one result = processing.runalg('qgis:dissolve', frameLayer, True, None, None) # get the layer from processing result outputLayer = processing.getObject(result['OUTPUT']) # clean possible missinformation from resulting layer # setting clean parameters tools = 'rmsa,break,rmdupl,rmdangle' threshold = -1 extent = outputLayer.extent() (xmin, xmax, ymin, ymax) = extent.xMinimum(), extent.xMaximum( ), extent.yMinimum(), extent.yMaximum() extent = '{0},{1},{2},{3}'.format(xmin, xmax, ymin, ymax) snap = self.parameters['Search Radius'] minArea = 0.0001 result = processing.runalg('grass7:v.clean.advanced', outputLayer, tools, threshold, extent, snap, minArea, None, None) # get resulting layer outputLayer = processing.getObject(result['output']) # get all frame outter layer found for feat in outputLayer.getFeatures(): geom = feat.geometry() # deaggregate geometry, if necessary geomList = self.DsgGeometryHandler.deaggregateGeometry( multiGeom=geom) # for every deaggregated node, get only the outter bound as a polyline (for intersection purposes) frameGeomList += [ QgsGeometry().fromPolyline(g.asPolygon()[0]) for g in geomList ] return frameGeomList def identifyAllNodes(self, networkLayer): """ Identifies all nodes from a given layer (or selected features of it). The result is returned as a dict of dict. :param networkLayer: target layer to which nodes identification is required. :return: { node_id : { start : [feature_which_starts_with_node], end : feature_which_ends_with_node } }. """ nodeDict = dict() isMulti = QgsWKBTypes.isMultiType(int(networkLayer.wkbType())) if self.parameters['Only Selected']: features = networkLayer.selectedFeatures() else: features = [feat for feat in networkLayer.getFeatures()] for feat in features: nodes = self.DsgGeometryHandler.getFeatureNodes(networkLayer, feat) if nodes: if isMulti: if len(nodes) > 1: # if feat is multipart and has more than one part, a flag should be raised continue # CHANGE TO RAISE FLAG elif len(nodes) == 0: # if no part is found, skip feature continue else: # if feat is multipart, "nodes" is a list of list nodes = nodes[0] # initial node pInit, pEnd = nodes[0], nodes[-1] # filling starting node information into dictionary if pInit not in nodeDict: # if the point is not already started into dictionary, it creates a new item nodeDict[pInit] = {'start': [], 'end': []} if feat not in nodeDict[pInit]['start']: nodeDict[pInit]['start'].append(feat) # filling ending node information into dictionary if pEnd not in nodeDict: nodeDict[pEnd] = {'start': [], 'end': []} if feat not in nodeDict[pEnd]['end']: nodeDict[pEnd]['end'].append(feat) return nodeDict def changeLineDict(self, nodeList, line): """ Changes a line from a dict to another (start/end). Useful when network lines are flipped and another node identification is too costy. :param nodeList: (list-of-QgsPoint) the list of nodes connected to have given line relation dictionary changed. :param line: (QgsFeature) line to be flipped. :return: whether changing was successful for each node """ for node in nodeList: # node's dicts startDict = self.nodeDict[node]['start'] endDict = self.nodeDict[node]['end'] if line in startDict: startDict.remove(line) endDict.append(line) elif line in endDict: startDict.append(line) endDict.remove(line) else: # if line is not found for some reason return False # if a nodeList is not found, method doesn't change anything return bool(nodeList) def nodeOnFrame(self, node, frameLyrContourList, searchRadius): """ Identify whether or not node is over the frame. Returns True if point is over the frame and false if node is not on frame. If identification fails, returns 'None'. :param node: node (QgsPoint) to be identified as over the frame layer or not. :param frameLyrContourList: (list-of-QgsGeometry) border line for the frame layer to be checked. :param searchRadius: maximum distance to frame layer such that the feature is considered touching it. :return: (bool) whether node is as close as searchRaius to frame contour. """ qgisPoint = QgsGeometry.fromPoint(node) # building a buffer around node with search radius for intersection with Layer Frame buf = qgisPoint.buffer(searchRadius, -1) for frameContour in frameLyrContourList: if buf.intersects(frameContour): # it is condition enough one of the frame contours to be next to node return True return False def nodeNextToWaterBodies(self, node, waterBodiesLayers, searchRadius): """ Identify whether or not node is next to a water body feature. :param node: (QgsPoint) node to be identified as next to a water body feature. :param waterBodiesLayers: (list-of-QgsVectorLayer) list of layers composing the water bodies on map. :param searchRadius: (float) maximum distance to frame layer such that the feature is considered touching it. :return: (bool) whether node is as close as searchRaius to a water body element. """ qgisPoint = QgsGeometry.fromPoint(node) # building a buffer around node with search radius for intersection with Layer Frame buf = qgisPoint.buffer(searchRadius, -1) # building bounding box around node for feature requesting bbRect = buf.boundingBox() # check if buffer intersects features from water bodies layers for lyr in waterBodiesLayers: if lyr.geometryType() == 0: # ignore point primitive layers continue for feat in lyr.getFeatures(QgsFeatureRequest(bbRect)): if buf.intersects(feat.geometry()): # any feature component of a water body intersected is enough return True return False def nodeIsWaterSink(self, node, waterSinkLayer, searchRadius): """ Identify whether or not node is next to a water body feature. If no water sink layer is given, method returns False :param node: (QgsPoint) node to be identified as coincident with a water sink feature. :param waterSinkLayer: (QgsVectorLayer) layer containing the water sinks on map. :param searchRadius: (float) maximum distance to frame layer such that the feature is considered touching it. :return: (bool) whether node is as close as searchRaius to a water body element. """ if not waterSinkLayer: return False qgisPoint = QgsGeometry.fromPoint(node) # building bounding box around node for feature requesting bbRect = qgisPoint.buffer(searchRadius, -1).boundingBox() # check if qgisPoint (node geometry) is over a sink classified point for feat in waterSinkLayer.getFeatures(QgsFeatureRequest(bbRect)): if qgisPoint.distance(feat.geometry()) <= searchRadius: # any feature component of a water body intersected is enough return True return False def checkIfHasLineInsideWaterBody(self, node, waterBodiesLayers, searchRadius=1.0): """ Checks whether one of ending lines connected to given node is inside of a water body feature. :param node: (QgsPoint) node to be identified having an ending line inside of a water body. :param waterBodiesLayers: (list-of-QgsVectorLayer) list of layers composing the water bodies on map. :return: (bool) whether node is as close as searchRaius to a water body element. """ qgisPoint = QgsGeometry.fromPoint(node) # building a buffer around node with search radius for intersection with Layer Frame buf = qgisPoint.buffer(searchRadius, -1) # building bounding box around node for feature requesting bbRect = buf.boundingBox() # check if any wb feature inside of buffer area contains any ending line for line in self.nodeDict[node]['end']: for lyr in waterBodiesLayers: for feat in lyr.getFeatures(QgsFeatureRequest(bbRect)): if feat.geometry().contains(line.geometry()): # any feature component of a water body intersected is enough return True return False def getAttributesFromFeature(self, feature, layer, fieldNames=None): """ Retrieves the attributes from a given feature, except for their geometry and ID column values. If a list of attributes is given, method will return those attributes if found. In case no attribute is found, None will :param feature: (QgsFeature) feature from which attibutes will be retrieved. :param layer: (QgsVectorLayer) layer containing target feature. :param fieldNames: (list-of-str) list of field names to be exposed. :return: (dict-of-object) attribute values for each attribute mapped. """ # fields to be ignored ignoreList = [] if not fieldNames: # retrieving key column name uri = QgsDataSourceURI(layer.dataProvider().dataSourceUri()) keyColumn = uri.keyColumn() # retrieving geometry column name networLayerName = layer.name() for key in self.networkClassesWithElemDict.keys(): if key.split(",")[1] in networLayerName: geomColumn = key.split(",")[2] break # removing attributes that are calculated OTF fieldNames = [ f for f in layer.fields() if f.name() not in [keyColumn, geomColumn] and '_otf' not in f.name() ] else: # check if all field names given are in fact fields for the layer layerFields = layer.fields() ignoreList = [ field for field in fieldNames if field not in layerFields ] return { field.name(): feature[field.name()] for field in fieldNames if field not in ignoreList } def attributeChangeCheck(self, node, networkLayer): """ Checks if attribute change node is in fact an attribute change. :param node: (QgsPoint) node to be identified as over the frame layer or not. :param networkLayer: (QgsVectorLayer) layer containing network lines. :return: (bool) if lines connected to node do change attributes. """ # assuming that attribute change nodes have only 1-in 1-out lines lineIn = self.nodeDict[node]['end'][0] atrLineIn = self.getAttributesFromFeature(feature=lineIn, layer=networkLayer) lineOut = self.nodeDict[node]['start'][0] atrLineOut = self.getAttributesFromFeature(feature=lineOut, layer=networkLayer) # comparing their dictionary of attributes, it is decided whether they share the exact same set of attributes (fields and values) return atrLineIn != atrLineOut def isFirstOrderDangle(self, node, networkLayer, searchRadius): """ Checks whether node is a dangle into network (connected to a first order line). :param node: (QgsPoint) node to be validated. :param networkLayer: (QgsVectorLayer) network layer (line layer). :param searchRadius: (float) limit distance to another line. :return: (bool) indication whether node is a dangle. """ qgisPoint = QgsGeometry.fromPoint(node) # building a buffer around node with search radius for intersection with Layer Frame buf = qgisPoint.buffer(searchRadius, -1) # building bounding box around node for feature requesting bbRect = buf.boundingBox() # check if buffer intersects features from water bodies layers count = 0 for feat in networkLayer.getFeatures(QgsFeatureRequest(bbRect)): if buf.intersects(feat.geometry()): count += 1 res = (count > 1) if res: # to avoid as many iterations as possible return False return True def checkIfLineIsDisconnected(self, node, networkLayer, nodeTypeDict, geomType=None): """ Checks whether a waterway beginning node connected to a line disconnected from network. :param node: (QgsPoint) point to be classified. :param networkLayer: (QgsVectorLayer) network lines layer. :param nodeTypeDict: (dict) all current classified nodes and theirs types. :param geomType: (int) network layer geometry type code. :return: (bool) whether node is connected to a disconnected line """ if not nodeTypeDict: # if there are no classified nodes, method is ineffective return False # if a line is disconnected from network, then the other end of the line would have to be classified as a waterway beginning as well if not geomType: geomType = networkLayer.geometryType() nextNodes = [] # to reduce calculation time nodePointDict = self.nodeDict[node] isMulti = QgsWKBTypes.isMultiType(int(networkLayer.wkbType())) # get all other nodes connected to lines connected to "node" lines = nodePointDict['start'] + nodePointDict['end'] if len(lines) > 1: # if there is at least one more line connected to node, line is not disconnected return False # get line nodes n = self.DsgGeometryHandler.getFeatureNodes(layer=networkLayer, feature=lines[0], geomType=geomType) if nodePointDict['start']: # if line starts at target node, the other extremity is a final node if isMulti: if n: n = n[0][-1] elif n: n = n[-1] elif nodePointDict['end']: # if line starts at target node, the other extremity is a initial node if isMulti: if n: n = n[0][0] elif n: n = n[0] # if next node is not among the valid ending lines, it may still be connected to a disconnected line if it is a dangle # validEnds = [CreateNetworkNodesProcess.Sink, CreateNetworkNodesProcess.DownHillNode, CreateNetworkNodesProcess.NodeNextToWaterBody] if n in nodeTypeDict: # if both ends are classified as waterway beginning, then both ends are 1st order dangles and line is disconnected. return nodeTypeDict[n] in [ CreateNetworkNodesProcess.WaterwayBegin, CreateNetworkNodesProcess.DownHillNode, CreateNetworkNodesProcess.UpHillNode, CreateNetworkNodesProcess.NodeNextToWaterBody ] # if nodeTypeDict[n] not in validEnds: # if self.isFirstOrderDangle(node=n, networkLayer=networkLayer, searchRadius=self.parameters[self.tr('Search Radius')]): # # if next node is not a valid network ending node and is a dangle, line is disconnected from network # return False # return True # in case next node is not yet classified, method is ineffective return False def nodeType(self, nodePoint, networkLayer, frameLyrContourList, waterBodiesLayers, searchRadius, nodeTypeDict, waterSinkLayer=None, networkLayerGeomType=None): """ Get the node type given all lines that flows from/to it. :param nodePoint: (QgsPoint) point to be classified. :param networkLayer: (QgsVectorLayer) network lines layer. :param frameLyrContourList: (list-of-QgsGeometry) border line for the frame layer to be checked. :param waterBodiesLayers: (list-of-QgsVectorLayer) list of all waterbodies layer. :param searchRadius: (float) maximum distance to frame layer such that the feature is considered touching it. :param nodeTypeDict: (dict) dict with all currently classified nodes and their types. :param waterSinkLayer: (QgsVectorLayer) water sink layer. :param networkLayerGeomType: (int) network layer geometry type code. :return: returns the (int) point type. """ # to reduce calculation time in expense of memory, which is cheap nodePointDict = self.nodeDict[nodePoint] sizeFlowOut = len(nodePointDict['start']) sizeFlowIn = len(nodePointDict['end']) hasStartLine = bool(sizeFlowOut) hasEndLine = bool(sizeFlowIn) if not networkLayerGeomType: networkLayerGeomType = networkLayer.geometryType() # "exclusive or" startXORendLine = (hasStartLine != hasEndLine) # # case 5: more than 3 lines flowing through one network line (it is forbidden as of Brazilian mapping norm EDGV) # if sizeFlowIn + sizeFlowOut > 3: # return CreateNetworkNodesProcess.NodeOverload # case 1: all lines either flow in or out if startXORendLine: # case 1.a: point is over the frame if self.nodeOnFrame(node=nodePoint, frameLyrContourList=frameLyrContourList, searchRadius=searchRadius): # case 1.a.i: waterway is flowing away from mapped area (point over the frame has one line ending line) if hasEndLine: return CreateNetworkNodesProcess.DownHillNode # case 1.a.ii: waterway is flowing to mapped area (point over the frame has one line starting line) elif hasStartLine: return CreateNetworkNodesProcess.UpHillNode # case 1.b: point that legitimately only flows from elif hasEndLine: # case 1.b.i if self.nodeNextToWaterBodies( node=nodePoint, waterBodiesLayers=waterBodiesLayers, searchRadius=searchRadius): # it is considered that every free node on map is a starting node. The only valid exceptions are nodes that are # next to water bodies and water sink holes. if sizeFlowIn == 1: # a node next to water has to be a lose end return CreateNetworkNodesProcess.NodeNextToWaterBody # force all lose ends to be waterway beginnings if they're not dangles (which are flags) elif self.isFirstOrderDangle( node=nodePoint, networkLayer=networkLayer, searchRadius=self.parameters['Search Radius']): # check if node is connected to a disconnected line if self.checkIfLineIsDisconnected( node=nodePoint, networkLayer=networkLayer, nodeTypeDict=nodeTypeDict, geomType=networkLayerGeomType): return CreateNetworkNodesProcess.DisconnectedLine # case 1.b.ii: node is in fact a water sink and should be able to take an 'in' flow elif self.nodeIsWaterSink(node=nodePoint, waterSinkLayer=waterSinkLayer, searchRadius=searchRadius): # if a node is indeed a water sink (operator has set it to a sink) return CreateNetworkNodesProcess.Sink return CreateNetworkNodesProcess.WaterwayBegin # case 1.c: point that legitimately only flows out elif hasStartLine and self.isFirstOrderDangle( node=nodePoint, networkLayer=networkLayer, searchRadius=self.parameters['Search Radius']): if self.checkIfLineIsDisconnected( node=nodePoint, networkLayer=networkLayer, nodeTypeDict=nodeTypeDict, geomType=networkLayerGeomType): return CreateNetworkNodesProcess.DisconnectedLine elif self.nodeIsWaterSink(node=nodePoint, waterSinkLayer=waterSinkLayer, searchRadius=searchRadius): # in case there's a wrongly acquired line connected to a water sink return CreateNetworkNodesProcess.Sink return CreateNetworkNodesProcess.WaterwayBegin # case 1.d: points that are not supposed to have one way flow (flags) return CreateNetworkNodesProcess.Flag elif sizeFlowIn > sizeFlowOut: # case 2 "confluence" return CreateNetworkNodesProcess.Confluence elif sizeFlowIn == sizeFlowOut: if sizeFlowIn > 1: # case 4.c: there's a constant flow through node, but there are more than 1 line return CreateNetworkNodesProcess.NodeOverload elif self.attributeChangeCheck(node=nodePoint, networkLayer=networkLayer): # case 4.a: lines do change their attribute set return CreateNetworkNodesProcess.AttributeChange else: # case 4.b: nodes inside the network that are there as an attribute change node but lines connected # to it have the same set of attributes return CreateNetworkNodesProcess.AttributeChangeFlag else: # case 3 "ramification" return CreateNetworkNodesProcess.Ramification def classifyAllNodes(self, networkLayer, frameLyrContourList, waterBodiesLayers, searchRadius, waterSinkLayer=None, nodeList=None): """ Classifies all identified nodes from the hidrography line layer. :param networkLayer: (QgsVectorLayer) network lines layer. :param frameLyrContourList: (list-of-QgsFeature) border line for the frame layer. :param waterBodiesLayers: (list-of-QgsVectorLayer) list of all classes with water bodies to be compared to. :param searchRadius: (float) maximum distance to frame layer such that the feature is considered touching it. :param nodeList: a list of nodes (QgsPoint) to be classified. If not given, whole dict is going to be classified. Node MUST be in dict given, if not, it'll be ignored. :param waterSinkLayer: (QgsVectorLayer) water sink layer. :return: a (dict) dictionary of node and its node type ( { (QgsPoint)node : (int)nodeType } ). """ networkLayerGeomType = networkLayer.geometryType() nodeTypeDict = dict() nodeKeys = self.nodeDict.keys() if not nodeList: nodeList = nodeKeys for node in nodeList: if node not in nodeKeys: # in case user decides to use a list of nodes to work on, given nodes that are not identified will be ignored continue nodeTypeDict[node] = self.nodeType(nodePoint=node, networkLayer=networkLayer, frameLyrContourList=frameLyrContourList, \ waterBodiesLayers=waterBodiesLayers, searchRadius=searchRadius, waterSinkLayer=waterSinkLayer, \ nodeTypeDict=nodeTypeDict, networkLayerGeomType=networkLayerGeomType) return nodeTypeDict def clearHidNodeLayer(self, nodeLayer, nodeIdList=None, commitToLayer=False): """ Clears all (or a given list of points) hidrography nodes on layer. :param nodeLayer: (QgsVectorLayer) hidrography nodes layer. :param nodeIdList: (list-of-int) list of node IDs to be cleared from layer. :param commitToLayer: (bool) indicates whether changes should be commited to layer. """ nodeLayer.beginEditCommand('Clear Nodes') if not nodeIdList: nodeIdList = [feat.id() for feat in nodeLayer.getFeatures()] nodeLayer.deleteFeatures(nodeIdList) nodeLayer.endEditCommand() # commit changes to LAYER if commitToLayer: nodeLayer.commitChanges() def fillNodeLayer(self, nodeLayer, networkLineLayerName, commitToLayer=False): """ Populate hidrography node layer with all nodes. :param nodeLayer: (QgsVectorLayer) hidrography nodes layer. :param networkLineLayerName: (str) network line layer name. :param commitToLayer: (bool) indicates whether changes should be commited to layer. """ # if table is going to be filled, then it needs to be cleared first self.clearHidNodeLayer(nodeLayer=nodeLayer, commitToLayer=commitToLayer) # get fields from layer in order to create new feature with the same attribute map fields = nodeLayer.fields() nodeLayer.beginEditCommand('Create Nodes') # to avoid unnecessary calculation inside loop nodeTypeKeys = self.nodeTypeDict.keys() # initiate new features list featList = [] for node in self.nodeDict: # set attribute map feat = QgsFeature(fields) # set geometry feat.setGeometry(QgsGeometry.fromMultiPoint([node])) feat['node_type'] = self.nodeTypeDict[ node] if node in nodeTypeKeys else None feat['layer'] = networkLineLayerName featList.append(feat) nodeLayer.addFeatures(featList) nodeLayer.endEditCommand() if commitToLayer: nodeLayer.commitChanges() def loadLayer(self, layerName, uniqueLoad=True): """ Load a given layer to canvas. :param layerName: (str) layer name to be loaded. :param uniqueLoad: (bool) indicates that layer will be loaded to canvas only if it is not loaded already. :return: (QgsVectorLayer) the loaded layer. """ try: return self.layerLoader.load([layerName], uniqueLoad=uniqueLoad)[layerName] except Exception as e: errorMsg = self.tr( 'Could not load the class {0}! (If you manually removed {0} from database, reloading QGIS/DSGTools Plugin might sort out the problem.\n' ).format(layerName) + ':'.join(e.args) QMessageBox.critical(self.canvas, self.tr('Error!'), errorMsg) def execute(self): """ Structures and executes the process. :return: (int) execution code. """ QgsMessageLog.logMessage( self.tr('Starting ') + self.getName() + self.tr(' Process.'), "DSG Tools Plugin", QgsMessageLog.CRITICAL) self.startTimeCount() try: self.setStatus(self.tr('Running'), 3) #now I'm running! self.abstractDb.deleteProcessFlags( self.getName()) #erase previous flags # node type should not be calculated OTF for comparison (db data is the one perpetuated) # setting all method variables networkLayerKey = self.parameters['Network Layer'] hidSinkLyrKey = self.parameters['Sink Layer'] refKey, classesWithElemKeys = self.parameters[ 'Reference and Water Body Layers'] waterBodyClassesKeys = classesWithElemKeys if not refKey: self.setStatus( self.tr('One reference must be selected! Stopping.'), 1) #Finished return 1 # preparing reference layer refcl = self.classesWithElemDict[refKey] frameLayer = self.loadLayerBeforeValidationProcess(refcl) # preparing hidrography lines layer # remake the key from standard string k = ('{},{},{},{},{}').format( networkLayerKey.split('.')[0],\ networkLayerKey.split('.')[1].split(r' (')[0],\ networkLayerKey.split('(')[1].split(', ')[0],\ networkLayerKey.split('(')[1].split(', ')[1],\ networkLayerKey.split('(')[1].split(', ')[2].replace(')', '') ) hidcl = self.networkClassesWithElemDict[k] networkLayer = self.loadLayerBeforeValidationProcess(hidcl) # preparing the list of water bodies classes waterBodyClasses = [] for key in waterBodyClassesKeys: wbc = self.classesWithElemDict[key] waterBodyClasses.append( self.loadLayerBeforeValidationProcess(wbc)) # preparing water sink layer if hidSinkLyrKey and hidSinkLyrKey != self.tr('Select Layer'): # remake the key from standard string k = ('{},{},{},{},{}').format( hidSinkLyrKey.split('.')[0],\ hidSinkLyrKey.split('.')[1].split(r' (')[0],\ hidSinkLyrKey.split('(')[1].split(', ')[0],\ hidSinkLyrKey.split('(')[1].split(', ')[1],\ hidSinkLyrKey.split('(')[1].split(', ')[2].replace(')', '') ) sinkcl = self.sinkClassesWithElemDict[k] waterSinkLayer = self.loadLayerBeforeValidationProcess(sinkcl) else: # if no sink layer is selected, layer should be ignored waterSinkLayer = None # getting dictionaries of nodes information frame = self.getFrameOutterBounds(frameLayer=frameLayer) self.nodeDict = self.identifyAllNodes(networkLayer=networkLayer) crs = networkLayer.crs().authid() # node layer has the same CRS as the hidrography lines layer nodeSrid = networkLayer.crs().authid().split(':')[1] searchRadius = self.parameters['Search Radius'] self.nodeTypeDict = self.classifyAllNodes( networkLayer=networkLayer, frameLyrContourList=frame, waterBodiesLayers=waterBodyClasses, searchRadius=searchRadius, waterSinkLayer=waterSinkLayer) # check if node table and node type domain table are created on db if not self.abstractDb.checkIfTableExists('validation', self.hidNodeLayerName): # if it does not exist, it is created self.abstractDb.createHidNodeTable(nodeSrid) # load node table into canvas nodeLayer = self.loadLayer(self.hidNodeLayerName) # to be able to update features into new layer nodeLayer.startEditing() self.fillNodeLayer(nodeLayer=nodeLayer, networkLineLayerName=networkLayer.name()) msg = self.tr('Network nodes created into layer {}.').format( self.hidNodeLayerName) self.setStatus(msg, 1) #Finished return 1 except Exception as e: QgsMessageLog.logMessage(': '.join(e.args), "DSG Tools Plugin", QgsMessageLog.CRITICAL) self.finishedWithError() return 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.dsgGeometryHandler = DsgGeometryHandler(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() if not setting: return False if setting.lower() == u'false': return False else: return True def setRubberbandParameters(self): self.rubberBand = QgsRubberBand(self.canvas, QGis.Polygon) self.hoverRubberBand = QgsRubberBand(self.canvas, QGis.Polygon) 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(QGis.Polygon) 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 QtGui.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(QGis.Polygon) if startPoint.x() == endPoint.x() or startPoint.y() == endPoint.y(): return point1 = QgsPoint(startPoint.x(), startPoint.y()) point2 = QgsPoint(startPoint.x(), endPoint.y()) point3 = QgsPoint(endPoint.x(), endPoint.y()) point4 = QgsPoint(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 QtGui.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.setSelectedFeatures([]) #portar para o feature handler layer.select(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 = lambda t = [field, layer] : self.handleFeatures(t[0], t[1]) 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: feature = QgsFeature(layer.fields()) self.dsgGeometryHandler.reprojectFeature(item['geom'], layer.crs()) feature.setGeometry(item['geom']) self.addFeature(feature, layer, selectedField, item['value']) self.auxList = [] self.canvas.refresh() def addFeature(self, feature, layer, field, pointValue): fields = layer.fields() feature.initAttributes(fields.count()) provider = layer.dataProvider() for i in range(fields.count()): value = provider.defaultValue(i) if fields[i].name() != field else pointValue if value: feature.setAttribute(i, value) form = QgsAttributeDialog(layer, feature, False) form.setMode(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, True) 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, True) 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. """ QtGui.QApplication.restoreOverrideCursor() self.hoverRubberBand.reset(QGis.Polygon) 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=QgsMessageBar.INFO, duration=10) self.deactivate() def reprojectSearchArea(self, layer, geom): """ Reprojects search area if necessary, according to what is being searched. :param layer: (QgsVectorLayer) layer which target rectangle has to have same SRC. :param geom: (QgsRectangle) rectangle representing search area. """ #geom always have canvas coordinates epsg = self.canvas.mapSettings().destinationCrs().authid() #getting srid from something like 'EPSG:31983' srid = layer.crs().authid() if epsg == srid: return geom crsSrc = QgsCoordinateReferenceSystem(epsg) crsDest = QgsCoordinateReferenceSystem(srid) # Creating a transformer coordinateTransformer = QgsCoordinateTransform(crsSrc, crsDest) # here we have to put authid, not srid auxGeom = QgsGeometry.fromRect(geom) auxGeom.transform(coordinateTransformer) return auxGeom.boundingBox() def getPixelValue(self, rasterLayer): mousePos = self.qgsMapToolEmitPoint.toMapCoordinates(self.canvas.mouseLastXY()) mousePosGeom = QgsGeometry.fromPoint(mousePos) return self.getPixelValueFromPoint(mousePosGeom, rasterLayer), mousePosGeom def getPixelValueFromPoint(self, mousePosGeom, rasterLayer, fromCanvas = True): """ """ rasterCrs = rasterLayer.crs() if rasterLayer else False if rasterCrs: if fromCanvas: self.dsgGeometryHandler.reprojectFeature(mousePosGeom, rasterCrs, self.canvas.mapRenderer().destinationCrs()) else: mousePosGeom = QgsGeometry(mousePosGeom) self.dsgGeometryHandler.reprojectFeature(mousePosGeom, rasterCrs, self.canvas.currentLayer().crs()) # isMulti = QgsWKBTypes.isMultiType(int(layer.wkbType())) # tem que ver se serve pra QgsGeometry 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 = 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.iteritems()} #no python3 eh items()
class BandValueTool(QgsMapTool): """ 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): """ Class constructor. """ # super(QgsRasterLayer, self).__init__() self.canvas = iface.mapCanvas() super(BandValueTool, self).__init__(self.canvas) self.parent = parent self.iface = iface self.toolAction = None self.QgsMapToolEmitPoint = QgsMapToolEmitPoint(self.canvas) self.DsgGeometryHandler = DsgGeometryHandler(iface) self.timerMapTips = QTimer( self.canvas ) self.timerMapTips.timeout.connect( self.showToolTip ) self.activated = False def setAction(self, action): """ """ self.toolAction = action def activate(self): """ Activates tool. """ if self.toolAction: self.activated = True QgsMapTool.activate(self) self.canvas.setMapTool(self) def deactivate(self): """ Deactivates tool. """ self.timerMapTips.stop() try: if self.toolAction: self.activated = False self.toolAction.setChecked(False) if self is not None: QgsMapTool.deactivate(self) except: pass def canvasMoveEvent(self, e): QToolTip.hideText() self.timerMapTips.start( 500 ) # time in milliseconds self.showToolTip() def getPixelValue(self, rasterLayer): """ """ rasterCrs = rasterLayer.crs() mousePos = self.QgsMapToolEmitPoint.toMapCoordinates(self.canvas.mouseLastXY()) mousePosGeom = QgsGeometry.fromPoint(mousePos) self.DsgGeometryHandler.reprojectFeature(mousePosGeom, rasterCrs, self.canvas.mapRenderer().destinationCrs()) 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 i.results().values() if r is not None] ) else: text = "" return text def showToolTip(self): """ """ self.timerMapTips.stop() if self.canvas.underMouse(): raster = self.parent.rasterComboBox.currentLayer() if raster: text = self.getPixelValue(raster) p = self.canvas.mapToGlobal( self.canvas.mouseLastXY() ) QToolTip.showText( p, text, self.canvas )