def mergeLines(self, line_a, line_b, layer): """ Merge 2 lines of the same layer (it is assumed that they share the same set od attributes - except for ID and geometry). In case sets are different, the set of attributes from line_a will be kept. If geometries don't touch, method is not applicable. :param line_a: (QgsFeature) main line of merging process. :param line_b: (QgsFeature) line to be merged to line_a. :param layer: (QgsVectorLayer) layer containing given lines. :return: (bool) True if method runs OK or False, if lines do not touch. """ # check if original layer is a multipart isMulti = QgsWKBTypes.isMultiType(int(layer.wkbType())) # retrieve lines geometries geometry_a = line_a.geometry() geometry_b = line_b.geometry() # checking the spatial predicate touches if geometry_a.touches(geometry_b): # this generates a multi geometry geometry_a = geometry_a.combine(geometry_b) # this make a single line string if the multi geometries are neighbors geometry_a = geometry_a.mergeLines() if isMulti: # making a "single" multi geometry (EDGV standard) geometry_a.convertToMultiType() # updating feature line_a.setGeometry(geometry_a) # remove the aggregated line to avoid overlapping layer.deleteFeature(line_b.id()) # updating layer layer.updateFeature(line_a) return True return False
def getFeatureNodes(self, layer, feature, geomType=None): """ Inverts the flow from a given feature. THE GIVEN FEATURE IS ALTERED. Standard behaviour is to not refresh canvas map. :param layer: layer containing the target feature for flipping. :param feature: feature to be flipped. :param geomType: if layer geometry type is not given, it'll calculate it (0,1 or 2). :returns: feature as of a list of points (nodes). """ if not geomType: geomType = layer.geometryType() # getting whether geometry is multipart or not isMulti = QgsWKBTypes.isMultiType(int(layer.wkbType())) geom = feature.geometry() if geomType == 0: if isMulti: nodes = geom.asMultiPoint() else: nodes = geom.asPoint() elif geomType == 1: if isMulti: nodes = geom.asMultiPolyline() else: nodes = geom.asPolyline() elif geomType == 2: if isMulti: nodes = geom.asMultiPolygon() else: nodes = geom.asPolygon() return nodes
def getFeatureNodes(self, layer, feature, geomType=None): """ Inverts the flow from a given feature. THE GIVEN FEATURE IS ALTERED. Standard behaviour is to not refresh canvas map. :param layer: layer containing the target feature for flipping. :param feature: feature to be flipped. :param geomType: if layer geometry type is not given, it'll calculate it (0,1 or 2). :returns: feature as of a list of points (nodes). """ if not geomType: geomType = layer.geometryType() # getting whether geometry is multipart or not isMulti = QgsWKBTypes.isMultiType(int(layer.wkbType())) geom = feature.geometry() if geomType == 0: if isMulti: nodes = geom.asMultiPoint() else: nodes = geom.asPoint() elif geomType == 1: if isMulti: nodes = geom.asMultiPolyline() else: nodes = geom.asPolyline() elif geomType == 2: if isMulti: nodes = geom.asMultiPolygon() else: nodes = geom.asPolygon() return nodes
def mergeLines(self, line_a, line_b, layer): """ Merge 2 lines of the same layer (it is assumed that they share the same set od attributes - except for ID and geometry). In case sets are different, the set of geometry of line_a will be kept. If geometries don't touch, method is not applicable. :param line_a: (QgsFeature) main line of merging process. :param line_b: (QgsFeature) line to be merged to line_a. :param layer: (QgsVectorLayer) layer containing given lines. :return: (bool) True if method runs OK or False, if lines do not touch. """ # check if original layer is a multipart isMulti = QgsWKBTypes.isMultiType(int(layer.wkbType())) # retrieve lines geometries geometry_a = line_a.geometry() geometry_b = line_b.geometry() # checking the spatial predicate touches if geometry_a.touches(geometry_b): # this generates a multi geometry geometry_a = geometry_a.combine(geometry_b) # this make a single line string if the multi geometries are neighbors geometry_a = geometry_a.mergeLines() if isMulti: # making a "single" multi geometry (EDGV standard) geometry_a.convertToMultiType() # updating feature line_a.setGeometry(geometry_a) # remove the aggregated line to avoid overlapping layer.deleteFeature(line_b.id()) # updating layer layer.updateFeature(line_a) return True return False
def flipFeature(self, layer, feature, geomType=None, refreshCanvas=False): """ Inverts the flow from a given feature. THE GIVEN FEATURE IS ALTERED. Standard behaviour is to not refresh canvas map. :param layer: layer containing the target feature for flipping. :param feature: feature to be flipped. :param geomType: if layer geometry type is not given, it'll calculate it (0,1 or 2) :param refreshCanvas: indicates whether the canvas should be refreshed after flipping feature. :returns: flipped feature as of [layer, feature, geometry_type]. """ if not geomType: geomType = layer.geometryType() # getting whether geometry is multipart or not isMulti = QgsWKBTypes.isMultiType(int(layer.wkbType())) geom = feature.geometry() if geomType == 0: if isMulti: nodes = geom.asMultiPoint() # inverting the point list by parts for idx, part in enumerate(nodes): nodes[idx] = part[::-1] # setting flipped geometry flippedFeatureGeom = QgsGeometry.fromMultiPoint(nodes) else: # inverting the point list nodes = geom.asPoint() nodes = nodes[::-1] flippedFeatureGeom = QgsGeometry.fromPoint(nodes) elif geomType == 1: if isMulti: nodes = geom.asMultiPolyline() for idx, part in enumerate(nodes): nodes[idx] = part[::-1] flippedFeatureGeom = QgsGeometry.fromMultiPolyline(nodes) else: nodes = geom.asPolyline() nodes = nodes[::-1] flippedFeatureGeom = QgsGeometry.fromPolyline(nodes) elif geomType == 2: if isMulti: nodes = geom.asMultiPolygon() for idx, part in enumerate(nodes): nodes[idx] = part[::-1] flippedFeatureGeom = QgsGeometry.fromMultiPolygon(nodes) else: nodes = geom.asPolygon() nodes = nodes[::-1] flippedFeatureGeom = QgsGeometry.fromPolygon(nodes) # setting feature geometry to the flipped one # feature.setGeometry(flippedFeatureGeom) # layer.updateFeature(feature) layer.changeGeometry(feature.id(), flippedFeatureGeom) if refreshCanvas: self.iface.mapCanvas().refresh() return [layer, feature, geomType]
def flipFeature(self, layer, feature, geomType=None, refreshCanvas=False): """ Inverts the flow from a given feature. THE GIVEN FEATURE IS ALTERED. Standard behaviour is to not refresh canvas map. :param layer: layer containing the target feature for flipping. :param feature: feature to be flipped. :param geomType: if layer geometry type is not given, it'll calculate it (0,1 or 2) :param refreshCanvas: indicates whether the canvas should be refreshed after flipping feature. :returns: flipped feature as of [layer, feature, geometry_type]. """ if not geomType: geomType = layer.geometryType() # getting whether geometry is multipart or not isMulti = QgsWKBTypes.isMultiType(int(layer.wkbType())) geom = feature.geometry() if geomType == 0: if isMulti: nodes = geom.asMultiPoint() # inverting the point list by parts for idx, part in enumerate(nodes): nodes[idx] = part[::-1] # setting flipped geometry flippedFeatureGeom = QgsGeometry.fromMultiPoint(nodes) else: # inverting the point list nodes = geom.asPoint() nodes = nodes[::-1] flippedFeatureGeom = QgsGeometry.fromPoint(nodes) elif geomType == 1: if isMulti: nodes = geom.asMultiPolyline() for idx, part in enumerate(nodes): nodes[idx] = part[::-1] flippedFeatureGeom = QgsGeometry.fromMultiPolyline(nodes) else: nodes = geom.asPolyline() nodes = nodes[::-1] flippedFeatureGeom = QgsGeometry.fromPolyline(nodes) elif geomType == 2: if isMulti: nodes = geom.asMultiPolygon() for idx, part in enumerate(nodes): nodes[idx] = part[::-1] flippedFeatureGeom = QgsGeometry.fromMultiPolygon(nodes) else: nodes = geom.asPolygon() nodes = nodes[::-1] flippedFeatureGeom = QgsGeometry.fromPolygon(nodes) # setting feature geometry to the flipped one # feature.setGeometry(flippedFeatureGeom) # layer.updateFeature(feature) layer.changeGeometry(feature.id(), flippedFeatureGeom) if refreshCanvas: self.iface.mapCanvas().refresh() return [layer, feature, geomType]
def createUnifiedLineLayer(self, layerList, onlySelected=False): """ For each layer in layerList, transforms it into lines if lyrType is polygon and adds features into layerList. Duplicates can happen in this process. """ srid = layerList[0].crs().authid().split(':')[-1] coverage = self.iface.addVectorLayer( "{0}?crs=epsg:{1}".format('Linestring', srid), "coverage_lines", "memory") provider = coverage.dataProvider() coverage.startEditing() coverage.beginEditCommand('Creating coverage lines layer') self.localProgress = ProgressWidget( 1, len(layerList) - 1, self.tr('Building unified layers with ') + ', '.join([i.name() for i in layerList]) + '.', parent=self.iface.mapCanvas()) addFeatureList = [] for lyr in layerList: isPolygon = True if lyr.dataProvider().geometryType() in [ QGis.WKBPolygon, QGis.WKBMultiPolygon ] else False isMulti = QgsWKBTypes.isMultiType(int(lyr.wkbType())) featureIterator = lyr.getFeatures( ) if not onlySelected else lyr.selectedFeatures() for feature in featureIterator: geom = feature.geometry() if geom: parts = geom.asGeometryCollection() if parts: for part in parts: if isPolygon: linestrings = part.asPolygon() for line in linestrings: newfeat = QgsFeature( coverage.pendingFields()) newfeat.setGeometry( QgsGeometry.fromPolyline(line)) addFeatureList.append(newfeat) else: newfeat = QgsFeature(coverage.pendingFields()) newfeat.setGeometry(part) addFeatureList.append(newfeat) self.localProgress.step() coverage.addFeatures(addFeatureList, True) coverage.endEditCommand() coverage.commitChanges() return coverage
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 reclassify(self): """ Performs the actual reclassification, moving the geometry to the correct layer along with the specified attributes """ if not self.checkConditions(): return somethingMade = False reclassifiedFeatures = 0 #button that sent the signal self.buttonName = self.sender().text() (reclassificationLayer, self.category, self.edgvClass) = self.getLayerFromButton(self.buttonName) geomType = reclassificationLayer.geometryType() hasMValues = QgsWKBTypes.hasM(int(reclassificationLayer.wkbType())) #generic check (not every database is implemented as ours) hasZValues = QgsWKBTypes.hasZ(int(reclassificationLayer.wkbType())) # isMulti = QgsWKBTypes.isMultiType(int(reclassificationLayer.wkbType())) # mapLayers = self.iface.mapCanvas().layers() crsSrc = QgsCoordinateReferenceSystem(self.widget.crs.authid()) deleteList = [] for mapLayer in mapLayers: if mapLayer.type() != QgsMapLayer.VectorLayer: continue #iterating over selected features featList = [] mapLayerCrs = mapLayer.crs() #creating a coordinate transformer (mapLayerCrs to crsSrc) coordinateTransformer = QgsCoordinateTransform(mapLayerCrs, crsSrc) for feature in mapLayer.selectedFeatures(): geomList = [] geom = feature.geometry() if geom.type() != geomType: continue if 'geometry' in dir(geom): if not hasMValues: geom.geometry().dropMValue() if not hasZValues: geom.geometry().dropZValue() if isMulti and not geom.isMultipart(): geom.convertToMultiType() geomList.append(geom) elif not isMulti and geom.isMultipart(): #deaggregate here parts = geom.asGeometryCollection() for part in parts: part.convertToSingleType() geomList.append(part) else: geomList.append(geom) for newGeom in geomList: #creating a new feature according to the reclassification layer newFeature = QgsFeature(reclassificationLayer.pendingFields()) #transforming the geometry to the correct crs geom.transform(coordinateTransformer) #setting the geometry newFeature.setGeometry(newGeom) #setting the attributes using the reclassification dictionary newFeature = self.setFeatureAttributes(newFeature) #adding the newly created feature to the addition list featList.append(newFeature) somethingMade = True deleteList.append({'originalLyr':mapLayer,'featid':feature.id()}) #actual feature insertion reclassificationLayer.addFeatures(featList, False) reclassifiedFeatures += len(featList) for item in deleteList: item['originalLyr'].startEditing() item['originalLyr'].deleteFeature(item['featid']) if somethingMade: self.iface.messageBar().pushMessage(self.tr('Information!'), self.tr('{} features reclassified with success!').format(reclassifiedFeatures), level=QgsMessageBar.INFO, duration=3)
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 updateOriginalLayerV2(self, pgInputLayer, qgisOutputVector, featureList=None, featureTupleList=None, deleteFeatures=True): """ Updates the original layer using the grass output layer pgInputLyr: postgis input layer qgisOutputVector: qgis output layer Speed up tips: http://nyalldawson.net/2016/10/speeding-up-your-pyqgis-scripts/ 1- Make pgIdList, by querying it with flag QgsFeatureRequest.NoGeometry 2- Build output dict 3- Perform operation """ provider = pgInputLayer.dataProvider() # getting keyColumn because we want to be generic uri = QgsDataSourceURI(pgInputLayer.dataProvider().dataSourceUri()) keyColumn = uri.keyColumn() # starting edition mode pgInputLayer.startEditing() pgInputLayer.beginEditCommand('Updating layer') addList = [] idsToRemove = [] inputDict = dict() #this is done to work generically with output layers that are implemented different from ours isMulti = QgsWKBTypes.isMultiType(int(pgInputLayer.wkbType())) # #making the changes and inserts #this request only takes ids to build inputDict request = QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry) for feature in pgInputLayer.getFeatures(request): inputDict[feature.id()] = dict() inputDict[feature.id()]['featList'] = [] inputDict[feature.id()]['featWithoutGeom'] = feature inputDictKeys = inputDict.keys() if qgisOutputVector: for feat in qgisOutputVector.dataProvider().getFeatures(): if keyColumn == '': featid = feat.id() else: featid = feat[keyColumn] if featid in inputDictKeys: #verificar quando keyColumn = '' inputDict[featid]['featList'].append(feat) elif featureTupleList: for gfid, gf in featureTupleList: if gfid in inputDictKeys and gf[ 'classname'] == pgInputLayer.name(): inputDict[gfid]['featList'].append(gf) else: for feat in featureList: if keyColumn == '': featid = feat.id() else: featid = feat[keyColumn] if featid in inputDictKeys: inputDict[featid]['featList'].append(feat) #finally, do what must be done for id in inputDictKeys: outFeats = inputDict[id]['featList'] #starting to make changes for i in range(len(outFeats)): if i == 0: #let's update this feature newGeom = outFeats[i].geometry() if newGeom: if isMulti: newGeom.convertToMultiType() pgInputLayer.changeGeometry( id, newGeom) #It is faster according to the api else: if id not in idsToRemove: idsToRemove.append(id) else: #for the rest, let's add them newFeat = QgsFeature(inputDict[id]['featWithoutGeom']) newGeom = outFeats[i].geometry() if newGeom: if isMulti and newGeom: newGeom.convertToMultiType() newFeat.setGeometry(newGeom) if keyColumn != '': idx = newFeat.fieldNameIndex(keyColumn) newFeat.setAttribute(idx, provider.defaultValue(idx)) addList.append(newFeat) else: if id not in idsToRemove: idsToRemove.append(id) #in the case we don't find features in the output we should mark them to be removed if len(outFeats) == 0 and deleteFeatures: idsToRemove.append(id) #pushing the changes into the edit buffer pgInputLayer.addFeatures(addList, True) #removing features from the layer. pgInputLayer.deleteFeatures(idsToRemove) pgInputLayer.endEditCommand()
def reclassify(self): """ Performs the actual reclassification, moving the geometry to the correct layer along with the specified attributes """ if not self.checkConditions(): return somethingMade = False reclassifiedFeatures = 0 #button that sent the signal self.buttonName = self.sender().text().split(' [')[0] (reclassificationLayer, self.category, self.edgvClass) = self.getLayerFromButton(self.buttonName) geomType = reclassificationLayer.geometryType() hasMValues = QgsWKBTypes.hasM(int(reclassificationLayer.wkbType())) #generic check (not every database is implemented as ours) hasZValues = QgsWKBTypes.hasZ(int(reclassificationLayer.wkbType())) # isMulti = QgsWKBTypes.isMultiType(int(reclassificationLayer.wkbType())) # mapLayers = self.iface.mapCanvas().layers() #we need to get the authid that thefines the ref system of destination layer crsSrc = QgsCoordinateReferenceSystem(reclassificationLayer.crs().authid()) deleteList = [] for mapLayer in mapLayers: if mapLayer.type() != QgsMapLayer.VectorLayer: continue #iterating over selected features featList = [] mapLayerCrs = mapLayer.crs() #creating a coordinate transformer (mapLayerCrs to crsSrc) coordinateTransformer = QgsCoordinateTransform(mapLayerCrs, crsSrc) for feature in mapLayer.selectedFeatures(): geomList = [] geom = feature.geometry() if geom.type() != geomType: continue if 'geometry' in dir(geom): if not hasMValues: geom.geometry().dropMValue() if not hasZValues: geom.geometry().dropZValue() if isMulti and not geom.isMultipart(): geom.convertToMultiType() geomList.append(geom) if not isMulti and geom.isMultipart(): #deaggregate here parts = geom.asGeometryCollection() for part in parts: part.convertToSingleType() geomList.append(part) else: geomList.append(geom) for newGeom in geomList: #creating a new feature according to the reclassification layer newFeature = QgsFeature(reclassificationLayer.pendingFields()) #transforming the geometry to the correct crs geom.transform(coordinateTransformer) #setting the geometry newFeature.setGeometry(newGeom) #setting the attributes using the reclassification dictionary newFeature = self.setFeatureAttributes(newFeature, oldFeat = feature) #adding the newly created feature to the addition list featList.append(newFeature) somethingMade = True deleteList.append({'originalLyr':mapLayer,'featid':feature.id()}) #actual feature insertion reclassificationLayer.addFeatures(featList, False) reclassifiedFeatures += len(featList) for item in deleteList: item['originalLyr'].startEditing() item['originalLyr'].deleteFeature(item['featid']) if somethingMade: self.iface.messageBar().pushMessage(self.tr('Information!'), self.tr('{} features reclassified with success!').format(reclassifiedFeatures), level=QgsMessageBar.INFO, duration=3)