def processAlgorithm(self, parameters, context, feedback): """ Here is where the processing itself takes place. """ layerHandler = LayerHandler() self.algRunner = AlgRunner() inputLyr = self.parameterAsVectorLayer(parameters, self.INPUT, context) if inputLyr is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT)) overlayLyr = self.parameterAsVectorLayer(parameters, self.OVERLAY, context) if overlayLyr is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.OVERLAY)) onlySelected = self.parameterAsBool(parameters, self.SELECTED, context) onlySelectedOverlay = self.parameterAsBool(parameters, self.SELECTED_OVERLAY, context) behavior = self.parameterAsEnum(parameters, self.BEHAVIOR, context) multiStepFeedback = QgsProcessingMultiStepFeedback(4, feedback) multiStepFeedback.setCurrentStep(0) multiStepFeedback.pushInfo(self.tr('Populating temp layer...')) auxLyr = layerHandler.createAndPopulateUnifiedVectorLayer( [inputLyr], geomType=inputLyr.wkbType(), onlySelected=onlySelected, feedback=multiStepFeedback) multiStepFeedback.setCurrentStep(1) if onlySelectedOverlay: overlayLyr = layerHandler.createAndPopulateUnifiedVectorLayer( [overlayLyr], geomType=overlayLyr.wkbType(), onlySelected=onlySelectedOverlay, feedback=multiStepFeedback) overlayLyr.startEditing() overlayLyr.renameAttribute(0, 'fid') overlayLyr.renameAttribute(1, 'cl') overlayLyr.commitChanges() # 1- check method # 2- if overlay and keep, use clip and symetric difference # 3- if remove outside, use clip # 4- if remove inside, use symetric difference multiStepFeedback.setCurrentStep(2) multiStepFeedback.pushInfo(self.tr('Running overlay...')) outputLyr = self.runOverlay(auxLyr, overlayLyr, behavior, context, multiStepFeedback) multiStepFeedback.setCurrentStep(3) multiStepFeedback.pushInfo(self.tr('Updating original layer...')) layerHandler.updateOriginalLayersFromUnifiedLayer( [inputLyr], outputLyr, feedback=multiStepFeedback, onlySelected=onlySelected) return {self.OUTPUT: inputLyr}
def processAlgorithm(self, parameters, context, feedback): """ Here is where the processing itself takes place. """ layerHandler = LayerHandler() algRunner = AlgRunner() 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 ) multiStepFeedback = QgsProcessingMultiStepFeedback(3, feedback) multiStepFeedback.setCurrentStep(0) multiStepFeedback.pushInfo(self.tr('Populating temp layer...')) auxLyr = layerHandler.createAndPopulateUnifiedVectorLayer( [inputLyr], geomType=inputLyr.wkbType(), onlySelected=onlySelected, feedback=multiStepFeedback ) multiStepFeedback.setCurrentStep(1) multiStepFeedback.pushInfo( self.tr( 'Snapping geometries from layer {input} to grid with size {tol}...' ).format(input=inputLyr.name(), tol=tol) ) snappedLayer = algRunner.runSnapToGrid( auxLyr, tol, context, feedback=multiStepFeedback ) multiStepFeedback.setCurrentStep(2) multiStepFeedback.pushInfo(self.tr('Updating original layer...')) layerHandler.updateOriginalLayersFromUnifiedLayer( [inputLyr], snappedLayer, feedback=multiStepFeedback, onlySelected=onlySelected ) return {self.OUTPUT: inputLyr}
def processAlgorithm(self, parameters, context, feedback): """ Here is where the processing itself takes place. """ layerHandler = LayerHandler() algRunner = AlgRunner() inputLyrList = self.parameterAsLayerList(parameters, self.INPUTLAYERS, context) if inputLyrList is None or inputLyrList == []: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUTLAYERS)) for layer in inputLyrList: if layer.featureCount() > 0: geomType = next(layer.getFeatures()).geometry().wkbType() break else: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUTLAYERS), self.tr("Provided layers have no features in it.")) onlySelected = self.parameterAsBool(parameters, self.SELECTED, context) snap = self.parameterAsDouble(parameters, self.TOLERANCE, context) minArea = self.parameterAsDouble(parameters, self.MINAREA, context) self.prepareFlagSink(parameters, inputLyrList[0], geomType, context) multiStepFeedback = QgsProcessingMultiStepFeedback(3, feedback) multiStepFeedback.setCurrentStep(0) multiStepFeedback.pushInfo(self.tr('Building unified layer...')) # in order to check the topology of all layers as a whole, all features # are handled as if they formed a single layer coverage = layerHandler.createAndPopulateUnifiedVectorLayer( inputLyrList, geomType=geomType, onlySelected=onlySelected, feedback=multiStepFeedback) multiStepFeedback.setCurrentStep(1) multiStepFeedback.pushInfo( self.tr('Running clean on unified layer...')) cleanedCoverage, error = algRunner.runClean(coverage, [ algRunner.RMSA, algRunner.Break, algRunner.RmDupl, algRunner.RmDangle ], context, returnError=True, snap=snap, minArea=minArea, feedback=multiStepFeedback) multiStepFeedback.setCurrentStep(2) multiStepFeedback.pushInfo(self.tr('Updating original layer...')) layerHandler.updateOriginalLayersFromUnifiedLayer( inputLyrList, cleanedCoverage, feedback=multiStepFeedback) self.flagCoverageIssues(cleanedCoverage, error, feedback) return {self.INPUTLAYERS: inputLyrList, self.FLAGS: self.flagSink}
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 processAlgorithm(self, parameters, context, feedback): """ Here is where the processing itself takes place. """ layerHandler = LayerHandler() algRunner = AlgRunner() inputLyrList = self.parameterAsLayerList(parameters, self.INPUTLAYERS, context) if inputLyrList is None or inputLyrList == []: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUTLAYERS)) onlySelected = self.parameterAsBool(parameters, self.SELECTED, context) tol = self.parameterAsDouble(parameters, self.TOLERANCE, context) multiStepFeedback = QgsProcessingMultiStepFeedback(5, feedback) multiStepFeedback.setCurrentStep(0) multiStepFeedback.pushInfo(self.tr('Building unified layer...')) coverage = layerHandler.createAndPopulateUnifiedVectorLayer( inputLyrList, geomType=QgsWkbTypes.MultiPolygon, onlySelected=onlySelected, feedback=multiStepFeedback) multiStepFeedback.setCurrentStep(1) multiStepFeedback.pushInfo( self.tr('Identifying dangles on {layer}...').format( layer=coverage.name())) dangleLyr = algRunner.runIdentifyDangles(coverage, tol, context, feedback=multiStepFeedback, onlySelected=onlySelected) multiStepFeedback.setCurrentStep(2) layerHandler.filterDangles(dangleLyr, tol, feedback=multiStepFeedback) multiStepFeedback.setCurrentStep(3) multiStepFeedback.pushInfo( self.tr('Snapping layer {layer} to dangles...').format( layer=coverage.name())) algRunner.runSnapLayerOnLayer(coverage, dangleLyr, tol, context, feedback=multiStepFeedback, onlySelected=onlySelected, behavior=0) multiStepFeedback.setCurrentStep(4) multiStepFeedback.pushInfo(self.tr('Updating original layers...')) layerHandler.updateOriginalLayersFromUnifiedLayer( inputLyrList, coverage, feedback=multiStepFeedback) return {self.INPUTLAYERS: inputLyrList}
def processAlgorithm(self, parameters, context, feedback): """ Here is where the processing itself takes place. """ layerHandler = LayerHandler() algRunner = AlgRunner() inputLyr = self.parameterAsVectorLayer(parameters, self.INPUT, context) onlySelected = self.parameterAsBool(parameters, self.SELECTED, context) tol = self.parameterAsDouble(parameters, self.MIN_AREA, context) attributeBlackList = self.parameterAsFields(parameters, self.ATTRIBUTE_BLACK_LIST, context) ignoreVirtual = self.parameterAsBool(parameters, self.IGNORE_VIRTUAL_FIELDS, context) ignorePK = self.parameterAsBool(parameters, self.IGNORE_PK_FIELDS, context) tol = -1 if tol is None else tol nSteps = 4 if tol > 0 else 3 multiStepFeedback = QgsProcessingMultiStepFeedback(nSteps, feedback) currentStep = 0 multiStepFeedback.setCurrentStep(currentStep) multiStepFeedback.pushInfo(self.tr('Populating temp layer...\n')) unifiedLyr = layerHandler.createAndPopulateUnifiedVectorLayer( [inputLyr], geomType=QgsWkbTypes.MultiPolygon, attributeBlackList=attributeBlackList, onlySelected=onlySelected, feedback=multiStepFeedback) currentStep += 1 if tol > 0: multiStepFeedback.setCurrentStep(currentStep) multiStepFeedback.pushInfo( self.tr('Adding size constraint field...\n')) unifiedLyr = layerHandler.addDissolveField( unifiedLyr, tol, feedback=multiStepFeedback) currentStep += 1 multiStepFeedback.setCurrentStep(currentStep) multiStepFeedback.pushInfo(self.tr('Running dissolve...\n')) dissolvedLyr = algRunner.runDissolve(unifiedLyr, context, feedback=multiStepFeedback, field=['tupple']) layerHandler.updateOriginalLayersFromUnifiedLayer( [inputLyr], dissolvedLyr, feedback=multiStepFeedback, onlySelected=onlySelected) return {self.OUTPUT: inputLyr}
def processAlgorithm(self, parameters, context, feedback): """ Here is where the processing itself takes place. """ layerHandler = LayerHandler() algRunner = AlgRunner() inputLyrList = self.parameterAsLayerList(parameters, self.INPUTLAYERS, context) if inputLyrList is None or inputLyrList == []: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUTLAYERS)) onlySelected = self.parameterAsBool(parameters, self.SELECTED, context) snap = self.parameterAsDouble(parameters, self.SNAP, context) threshold = self.parameterAsDouble(parameters, self.DOUGLASPARAMETER, context) minArea = self.parameterAsDouble(parameters, self.MINAREA, context) self.prepareFlagSink(parameters, inputLyrList[0], QgsWkbTypes.MultiPolygon, context) multiStepFeedback = QgsProcessingMultiStepFeedback(3, feedback) multiStepFeedback.setCurrentStep(0) multiStepFeedback.pushInfo(self.tr('Building unified layer...')) coverage = layerHandler.createAndPopulateUnifiedVectorLayer( inputLyrList, geomType=QgsWkbTypes.MultiPolygon, onlySelected=onlySelected, feedback=multiStepFeedback) multiStepFeedback.setCurrentStep(1) multiStepFeedback.pushInfo( self.tr('Running clean on unified layer...')) simplifiedCoverage, error = algRunner.runDouglasSimplification( coverage, threshold, context, returnError=True, snap=snap, minArea=minArea, feedback=multiStepFeedback) multiStepFeedback.setCurrentStep(2) multiStepFeedback.pushInfo(self.tr('Updating original layer...')) layerHandler.updateOriginalLayersFromUnifiedLayer( inputLyrList, simplifiedCoverage, feedback=multiStepFeedback, onlySelected=onlySelected) self.flagCoverageIssues(simplifiedCoverage, error, feedback) return {self.INPUTLAYERS: inputLyrList, self.FLAGS: self.flag_id}
def processAlgorithm(self, parameters, context, feedback): """ Here is where the processing itself takes place. """ layerHandler = LayerHandler() algRunner = AlgRunner() 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) snap = self.parameterAsDouble(parameters, self.TOLERANCE, context) # if snap < 0 and snap != -1: # raise QgsProcessingException(self.invalidParameterError(parameters, self.TOLERANCE)) minArea = self.parameterAsDouble(parameters, self.MINAREA, context) self.prepareFlagSink(parameters, inputLyr, inputLyr.wkbType(), context) multiStepFeedback = QgsProcessingMultiStepFeedback(3, feedback) multiStepFeedback.setCurrentStep(0) multiStepFeedback.pushInfo(self.tr('Populating temp layer...')) auxLyr = layerHandler.createAndPopulateUnifiedVectorLayer( [inputLyr], geomType=inputLyr.wkbType(), onlySelected=onlySelected, feedback=multiStepFeedback) multiStepFeedback.setCurrentStep(1) multiStepFeedback.pushInfo(self.tr('Running clean...')) cleanedLyr, error = algRunner.runClean(auxLyr, \ [algRunner.RMSA, algRunner.Break, algRunner.RmDupl, algRunner.RmDangle], \ context, \ returnError=True, \ snap=snap, \ minArea=minArea, feedback=multiStepFeedback) multiStepFeedback.setCurrentStep(2) multiStepFeedback.pushInfo(self.tr('Updating original layer...')) layerHandler.updateOriginalLayersFromUnifiedLayer( [inputLyr], cleanedLyr, feedback=multiStepFeedback, onlySelected=onlySelected) self.flagIssues(cleanedLyr, error, feedback) return {self.OUTPUT: inputLyr, 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)) 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() 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 IdentifyDanglesAlgorithm(ValidationAlgorithm): INPUT = 'INPUT' SELECTED = 'SELECTED' TOLERANCE = 'TOLERANCE' LINEFILTERLAYERS = 'LINEFILTERLAYERS' POLYGONFILTERLAYERS = 'POLYGONFILTERLAYERS' TYPE = 'TYPE' IGNOREINNER = 'IGNOREINNER' FLAGS = 'FLAGS' def initAlgorithm(self, config): """ Parameter setting. """ self.addParameter( QgsProcessingParameterVectorLayer(self.INPUT, self.tr('Input layer'), [QgsProcessing.TypeVectorLine])) self.addParameter( QgsProcessingParameterBoolean( self.SELECTED, self.tr('Process only selected features'))) self.addParameter( QgsProcessingParameterNumber( self.TOLERANCE, self.tr('Search radius'), minValue=0, type=QgsProcessingParameterNumber.Double, defaultValue=2)) self.addParameter( QgsProcessingParameterMultipleLayers( self.LINEFILTERLAYERS, self.tr('Linestring Filter Layers'), QgsProcessing.TypeVectorLine, optional=True)) self.addParameter( QgsProcessingParameterMultipleLayers( self.POLYGONFILTERLAYERS, self.tr('Polygon Filter Layers'), QgsProcessing.TypeVectorPolygon, optional=True)) self.addParameter( QgsProcessingParameterBoolean( self.TYPE, self.tr('Ignore dangle on unsegmented lines'))) self.addParameter( QgsProcessingParameterBoolean( self.IGNOREINNER, self.tr('Ignore search radius on inner layer search'))) self.addParameter( QgsProcessingParameterFeatureSink( self.FLAGS, self.tr('{0} Flags').format(self.displayName()))) def processAlgorithm(self, parameters, context, feedback): """ Here is where the processing itself takes place. """ self.layerHandler = LayerHandler() inputLyr = self.parameterAsVectorLayer(parameters, self.INPUT, context) onlySelected = self.parameterAsBool(parameters, self.SELECTED, context) searchRadius = self.parameterAsDouble(parameters, self.TOLERANCE, context) lineFilterLyrList = self.parameterAsLayerList(parameters, self.LINEFILTERLAYERS, context) polygonFilterLyrList = self.parameterAsLayerList( parameters, self.POLYGONFILTERLAYERS, context) ignoreNotSplit = self.parameterAsBool(parameters, self.TYPE, context) ignoreInner = self.parameterAsBool(parameters, self.IGNOREINNER, context) self.prepareFlagSink(parameters, inputLyr, QgsWkbTypes.Point, context) # Compute the number of steps to display within the progress bar and # get features from source feedbackTotal = 3 feedbackTotal += 1 if lineFilterLyrList or polygonFilterLyrList else 0 feedbackTotal += 1 if not ignoreInner else 0 multiStep = QgsProcessingMultiStepFeedback(feedbackTotal, feedback) currentStep = 0 multiStep.setCurrentStep(currentStep) multiStep.pushInfo(self.tr('Building search structure...')) endVerticesDict = self.layerHandler.buildInitialAndEndPointDict( inputLyr, onlySelected=onlySelected, feedback=multiStep) #search for dangles candidates currentStep += 1 multiStep.setCurrentStep(currentStep) multiStep.pushInfo(self.tr('Looking for dangles...')) pointList = self.searchDanglesOnPointDict(endVerticesDict, multiStep) #build filter layer currentStep += 1 multiStep.setCurrentStep(currentStep) multiStep.pushInfo(self.tr('Filtering dangles candidates...')) filterLayer = self.buildFilterLayer(lineFilterLyrList, polygonFilterLyrList, context, multiStep, onlySelected=onlySelected) #filter pointList with filterLayer if filterLayer: currentStep += 1 multiStep.setCurrentStep(currentStep) multiStep.pushInfo( self.tr('Filtering dangles candidates with filter...')) filteredPointList = self.filterPointListWithFilterLayer( pointList, filterLayer, searchRadius, multiStep) else: filteredPointList = pointList #filter with own layer if not ignoreInner: #True when looking for dangles on contour lines currentStep += 1 multiStep.setCurrentStep(currentStep) multiStep.pushInfo(self.tr('Filtering inner dangles...')) filteredPointList = self.filterPointListWithFilterLayer( filteredPointList, inputLyr, searchRadius, multiStep, isRefLyr=True, ignoreNotSplit=ignoreNotSplit) #build flag list with filtered points currentStep += 1 multiStep.setCurrentStep(currentStep) multiStep.pushInfo(self.tr('Raising flags...')) if filteredPointList: # currentValue = feedback.progress() currentTotal = 100 / len(filteredPointList) for current, point in enumerate(filteredPointList): if multiStep.isCanceled(): break self.flagFeature( QgsGeometry.fromPointXY(point), self.tr('Dangle on {0}').format(inputLyr.name())) multiStep.setProgress(current * currentTotal) # feedback.setProgress(100) return {self.FLAGS: self.flag_id} def searchDanglesOnPointDict(self, endVerticesDict, feedback): """ Counts the number of points on each endVerticesDict's key and returns a list of QgsPoint built from key candidate. """ pointList = [] nVertexes = len(endVerticesDict) localTotal = 100 / nVertexes if nVertexes else 0 # actual search for dangles for current, point in enumerate(endVerticesDict): if feedback.isCanceled(): break # this means we only have one occurrence of point, therefore it is a dangle if len(endVerticesDict[point]) <= 1: pointList.append(point) feedback.setProgress(localTotal * current) return pointList def buildFilterLayer(self, lineLyrList, polygonLyrList, context, feedback, onlySelected=False): """ Buils one layer of filter lines. Build unified layer is not used because we do not care for attributes here, only geometry. refLyr elements are also added. """ if not (lineLyrList + polygonLyrList): return [] lineLyrs = lineLyrList for polygonLyr in polygonLyrList: if feedback.isCanceled(): break lineLyrs += [self.makeBoundaries(polygonLyr, context, feedback)] if not lineLyrs: return None unifiedLinesLyr = self.layerHandler.createAndPopulateUnifiedVectorLayer( lineLyrs, QgsWkbTypes.MultiLineString, onlySelected=onlySelected) filterLyr = self.cleanLayer(unifiedLinesLyr, [0, 6], context) return filterLyr def makeBoundaries(self, lyr, context, feedback): parameters = {'INPUT': lyr, 'OUTPUT': 'memory:'} output = processing.run("native:boundary", parameters, context=context) return output['OUTPUT'] def cleanLayer(self, inputLyr, toolList, context, typeList=[0, 1, 2, 3, 4, 5, 6]): #TODO write one class that runs all processing stuff (model that tomorrow) output = QgsProcessingUtils.generateTempFilename('output.shp') error = QgsProcessingUtils.generateTempFilename('error.shp') parameters = { 'input': inputLyr, 'type': typeList, 'tool': toolList, 'threshold': '-1', '-b': False, '-c': True, 'output': output, 'error': error, 'GRASS_REGION_PARAMETER': None, 'GRASS_SNAP_TOLERANCE_PARAMETER': -1, 'GRASS_MIN_AREA_PARAMETER': 0.0001, 'GRASS_OUTPUT_TYPE_PARAMETER': 0, 'GRASS_VECTOR_DSCO': '', 'GRASS_VECTOR_LCO': '' } x = processing.run('grass7:v.clean', parameters, context=context) lyr = QgsProcessingUtils.mapLayerFromString(x['output'], context) return lyr def filterPointListWithFilterLayer(self, pointList, filterLayer, searchRadius, feedback, isRefLyr=False, ignoreNotSplit=False): """ Builds buffer areas from each point and evaluates the intersecting lines. If there are more than two intersections, it is a dangle. """ nPoints = len(pointList) localTotal = 100 / nPoints if nPoints else 0 spatialIdx, allFeatureDict = self.buildSpatialIndexAndIdDict( filterLayer) notDangleList = [] for current, point in enumerate(pointList): if feedback.isCanceled(): break candidateCount = 0 qgisPoint = QgsGeometry.fromPointXY(point) #search radius to narrow down candidates buffer = qgisPoint.buffer(searchRadius, -1) bufferBB = buffer.boundingBox() #gets candidates from spatial index candidateIds = spatialIdx.intersects(bufferBB) #if there is only one feat in candidateIds, that means that it is not a dangle bufferCount = len([ id for id in candidateIds if buffer.intersects(allFeatureDict[id].geometry()) ]) for id in candidateIds: if not isRefLyr: if buffer.intersects(allFeatureDict[id].geometry()) and \ qgisPoint.distance(allFeatureDict[id].geometry()) < 10**-9: #float problem, tried with intersects and touches and did not get results notDangleList.append(point) break else: if ignoreNotSplit: if buffer.intersects(allFeatureDict[id].geometry()) and \ (qgisPoint.distance(allFeatureDict[id].geometry()) < 10**-9 or \ qgisPoint.intersects(allFeatureDict[id].geometry())): #float problem, tried with intersects and touches and did not get results candidateCount += 1 else: if buffer.intersects(allFeatureDict[id].geometry()) and \ (qgisPoint.touches(allFeatureDict[id].geometry())): #float problem, tried with intersects and touches and did not get results candidateCount += 1 if candidateCount == bufferCount: notDangleList.append(point) feedback.setProgress(localTotal * current) filteredDangleList = [ point for point in pointList if point not in notDangleList ] return filteredDangleList def buildSpatialIndexAndIdDict(self, inputLyr): """ creates a spatial index for the centroid layer """ spatialIdx = QgsSpatialIndex() idDict = {} for feat in inputLyr.getFeatures(): spatialIdx.insertFeature(feat) idDict[feat.id()] = feat return spatialIdx, idDict def name(self): """ Returns the algorithm name, used for identifying the algorithm. This string should be fixed for the algorithm, and must not be localised. The name should be unique within each provider. Names should contain lowercase alphanumeric characters only and no spaces or other formatting characters. """ return 'identifydangles' def displayName(self): """ Returns the translated algorithm name, which should be used for any user-visible display of the algorithm name. """ return self.tr('Identify Dangles') def group(self): """ Returns the name of the group this algorithm belongs to. This string should be localised. """ return self.tr('Quality Assurance Tools (Identification Processes)') def groupId(self): """ Returns the unique ID of the group this algorithm belongs to. This string should be fixed for the algorithm, and must not be localised. The group id should be unique within each provider. Group id should contain lowercase alphanumeric characters only and no spaces or other formatting characters. """ return 'DSGTools: Quality Assurance Tools (Identification Processes)' def tr(self, string): return QCoreApplication.translate('IdentifyDanglesAlgorithm', string) def createInstance(self): return IdentifyDanglesAlgorithm()
def processAlgorithm(self, parameters, context, feedback): """ Here is where the processing itself takes place. """ layerHandler = LayerHandler() algRunner = AlgRunner() layerHandler = LayerHandler() inputLyr = self.parameterAsVectorLayer( parameters, self.INPUT, context ) onlySelected = self.parameterAsBool( parameters, self.SELECTED, context ) tol = self.parameterAsDouble( parameters, self.TOLERANCE, context ) attributeBlackList = self.parameterAsFields( parameters, self.ATTRIBUTE_BLACK_LIST, context ) ignoreVirtual = self.parameterAsBool( parameters, self.IGNORE_VIRTUAL_FIELDS, context ) ignorePK = self.parameterAsBool( parameters, self.IGNORE_PK_FIELDS, context ) layerHandler.mergeLinesOnLayer( inputLyr, feedback=feedback, onlySelected=onlySelected, ignoreVirtualFields=ignoreVirtual, attributeBlackList=attributeBlackList, excludePrimaryKeys=ignorePK ) #aux layer multiStepFeedback = QgsProcessingMultiStepFeedback(8, feedback) multiStepFeedback.setCurrentStep(0) if onlySelected: multiStepFeedback.pushInfo(self.tr('Building auxiliar layer...')) coverage = layerHandler.createAndPopulateUnifiedVectorLayer( [inputLyr], geomType=QgsWkbTypes.MultiPolygon, onlySelected=onlySelected, feedback=multiStepFeedback ) else: coverage = inputLyr #dangles multiStepFeedback.setCurrentStep(1) multiStepFeedback.pushInfo(self.tr('Identifying dangles on {layer}...').format(layer=coverage.name())) dangleLyr = algRunner.runIdentifyDangles( inputLayer=coverage, searchRadius=tol, context=context, feedback=multiStepFeedback, onlySelected=False ) #filter dangles multiStepFeedback.setCurrentStep(2) layerHandler.filterDangles( dangleLyr, tol, feedback=multiStepFeedback ) #snap layer to dangles multiStepFeedback.setCurrentStep(3) multiStepFeedback.pushInfo(self.tr('Snapping layer {layer} to dangles...').format(layer=coverage.name())) algRunner.runSnapLayerOnLayer( coverage, dangleLyr, tol, context, feedback=multiStepFeedback, onlySelected=False, #this is done due to the aux layer usage behavior=0 ) #inner layer snap multiStepFeedback.setCurrentStep(4) multiStepFeedback.pushInfo(self.tr('Snapping layer {layer} with {layer}...').format(layer=coverage.name())) algRunner.runSnapLayerOnLayer( coverage, coverage, tol, context, feedback=multiStepFeedback, onlySelected=False, #this is done due to the aux layer usage behavior=0 ) #clean to break lines multiStepFeedback.setCurrentStep(5) multiStepFeedback.pushInfo(self.tr('Running clean on {layer}...').format(layer=coverage.name())) multiStepFeedback.pushInfo(self.tr('Running clean on unified layer...')) cleanedCoverage, error = algRunner.runClean( coverage, [ algRunner.RMSA, algRunner.Break, algRunner.RmDupl, algRunner.RmDangle ], context, returnError=True, snap=snap, minArea=minArea, feedback=multiStepFeedback ) #remove duplicated features multiStepFeedback.setCurrentStep(6) multiStepFeedback.pushInfo(self.tr('Removing duplicated features from {layer}...').format(layer=coverage.name())) algRunner.runRemoveDuplicatedFeatures( inputLyr=cleanedCoverage, context=context, onlySelected=False, attributeBlackList=attributeBlackList, excludePrimaryKeys=excludePrimaryKeys, ignorePK=ignorePK, ignoreVirtual=ignoreVirtual ) #merging lines with same attributes multiStepFeedback.setCurrentStep(6) multiStepFeedback.pushInfo(self.tr('Merging lines from {layer} with same attribute set...').format(layer=coverage.name())) multiStepFeedback.setCurrentStep(7) multiStepFeedback.pushInfo(self.tr('Updating original layers...')) layerHandler.updateOriginalLayersFromUnifiedLayer( [inputLyr], coverage, feedback=multiStepFeedback, onlySelected=onlySelected ) return {self.INPUTLAYERS : inputLyrList}