def processAlgorithm(self, progress): layer = dataobjects.getObjectFromUri(self.getParameterValue(self.INPUT_LAYER)) input_wkb = layer.wkbType() if QgsWkbTypes.geometryType(input_wkb) == QgsWkbTypes.LineGeometry: output_wkb = QgsWkbTypes.MultiPoint elif QgsWkbTypes.geometryType(input_wkb) == QgsWkbTypes.PolygonGeometry: output_wkb = QgsWkbTypes.MultiLineString if QgsWkbTypes.hasZ(input_wkb): output_wkb = QgsWkbTypes.addZ(output_wkb) if QgsWkbTypes.hasM(input_wkb): output_wkb = QgsWkbTypes.addM(output_wkb) writer = self.getOutputFromName(self.OUTPUT_LAYER).getVectorWriter(layer.fields(), output_wkb, layer.crs()) features = vector.features(layer) total = 100.0 / len(features) for current, input_feature in enumerate(features): output_feature = input_feature input_geometry = input_feature.geometry() if input_geometry: output_geometry = QgsGeometry(input_geometry.geometry().boundary()) if not output_geometry: raise GeoAlgorithmExecutionException(self.tr("Error calculating boundary")) output_feature.setGeometry(output_geometry) writer.addFeature(output_feature) progress.setPercentage(int(current * total)) del writer
def _setRubberBandMarker(self, geom): m = QgsRubberBand(self.qgisIface.mapCanvas(), False) # not polygon if QgsWkbTypes.geometryType(geom.wkbType()) == QgsWkbTypes.LineGeometry: linegeom = geom elif QgsWkbTypes.geometryType(geom.wkbType()) == QgsWkbTypes.PolygonGeometry: linegeom = QgsGeometry.fromPolylineXY(geom.asPolygon()[0]) m.setToGeometry(linegeom, None) m.setColor(QColor(self.config['rubber_color'])) m.setWidth(self.config['rubber_width']) return m
def outputWkbType(self, input_wkb): if QgsWkbTypes.geometryType(input_wkb) == QgsWkbTypes.LineGeometry: output_wkb = QgsWkbTypes.MultiPoint elif QgsWkbTypes.geometryType(input_wkb) == QgsWkbTypes.PolygonGeometry: output_wkb = QgsWkbTypes.MultiLineString if QgsWkbTypes.hasZ(input_wkb): output_wkb = QgsWkbTypes.addZ(output_wkb) if QgsWkbTypes.hasM(input_wkb): output_wkb = QgsWkbTypes.addM(output_wkb) return output_wkb
def _setMarkerGeom(self, geom): if geom.isMultipart(): geometries = self._extractAsSingle(geom) for g in geometries: self._setMarkerGeom(g) else: if QgsWkbTypes.geometryType(geom.wkbType()) == QgsWkbTypes.PointGeometry: m = self._setPointMarker(geom) elif QgsWkbTypes.geometryType(geom.wkbType()) in (QgsWkbTypes.LineGeometry, QgsWkbTypes.PolygonGeometry): m = self._setRubberBandMarker(geom) self.markers.append( m )
def convertToLineStrings(self, geom): if QgsWkbTypes.geometryType(geom.wkbType()) == QgsWkbTypes.PointGeometry: raise QgsProcessingException( self.tr('Cannot convert from {0} to LineStrings').format(QgsWkbTypes.displayString(geom.wkbType()))) elif QgsWkbTypes.geometryType(geom.wkbType()) == QgsWkbTypes.LineGeometry: if QgsWkbTypes.isMultiType(geom.wkbType()): return geom.asGeometryCollection() else: #line to line return [geom] else: # polygons to lines # we just use the boundary here - that consists of all rings in the (multi)polygon boundary = QgsGeometry(geom.constGet().boundary()) # boundary will be multipart return boundary.asGeometryCollection()
def convertToMultiLineStrings(self, geom): if QgsWkbTypes.geometryType(geom.wkbType()) == QgsWkbTypes.PointGeometry: raise QgsProcessingException( self.tr('Cannot convert from {0} to MultiLineStrings').format(QgsWkbTypes.displayString(geom.wkbType()))) elif QgsWkbTypes.geometryType(geom.wkbType()) == QgsWkbTypes.LineGeometry: if QgsWkbTypes.isMultiType(geom.wkbType()): return [geom] else: # line to multiLine ml = QgsMultiLineString() ml.addGeometry(geom.constGet().clone()) return [QgsGeometry(ml)] else: # polygons to multilinestring # we just use the boundary here - that consists of all rings in the (multi)polygon return [QgsGeometry(geom.constGet().boundary())]
def convertToPolygon(self, geom): if QgsWkbTypes.geometryType(geom.wkbType()) == QgsWkbTypes.PointGeometry and geom.constGet().nCoordinates() < 3: raise QgsProcessingException( self.tr('Cannot convert from Point to Polygon').format(QgsWkbTypes.displayString(geom.wkbType()))) elif QgsWkbTypes.geometryType(geom.wkbType()) == QgsWkbTypes.PointGeometry: # multipoint with at least 3 points # TODO: mega inefficient - needs rework when geometry iterators land # (but at least it doesn't lose Z/M values) points = [] for g in geom.constGet().coordinateSequence(): for r in g: for p in r: points.append(p) linestring = QgsLineString(points) linestring.close() p = QgsPolygon() p.setExteriorRing(linestring) return [QgsGeometry(p)] elif QgsWkbTypes.geometryType(geom.wkbType()) == QgsWkbTypes.LineGeometry: if QgsWkbTypes.isMultiType(geom): parts = [] for i in range(geom.constGet().numGeometries()): p = QgsPolygon() linestring = geom.constGet().geometryN(i).clone() linestring.close() p.setExteriorRing(linestring) parts.append(QgsGeometry(p)) return QgsGeometry.collectGeometry(parts) else: # linestring to polygon p = QgsPolygon() linestring = geom.constGet().clone() linestring.close() p.setExteriorRing(linestring) return [QgsGeometry(p)] else: #polygon if QgsWkbTypes.isMultiType(geom): return geom.asGeometryCollection() else: return [geom]
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT_LAYER, context) input_wkb = source.wkbType() if QgsWkbTypes.geometryType(input_wkb) == QgsWkbTypes.LineGeometry: output_wkb = QgsWkbTypes.MultiPoint elif QgsWkbTypes.geometryType(input_wkb) == QgsWkbTypes.PolygonGeometry: output_wkb = QgsWkbTypes.MultiLineString if QgsWkbTypes.hasZ(input_wkb): output_wkb = QgsWkbTypes.addZ(output_wkb) if QgsWkbTypes.hasM(input_wkb): output_wkb = QgsWkbTypes.addM(output_wkb) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT_LAYER, context, source.fields(), output_wkb, source.sourceCrs()) features = source.getFeatures() total = 100.0 / source.featureCount() for current, input_feature in enumerate(features): if feedback.isCanceled(): break output_feature = input_feature input_geometry = input_feature.geometry() if input_geometry: output_geometry = QgsGeometry(input_geometry.geometry().boundary()) if not output_geometry: raise GeoAlgorithmExecutionException( self.tr('Error calculating boundary')) output_feature.setGeometry(output_geometry) sink.addFeature(output_feature) feedback.setProgress(int(current * total)) return {self.OUTPUT_LAYER: dest_id}
def processAlgorithm(self, context, feedback): layerA = dataobjects.getLayerFromString( self.getParameterValue(self.INPUT_A)) splitLayer = dataobjects.getLayerFromString( self.getParameterValue(self.INPUT_B)) sameLayer = self.getParameterValue( self.INPUT_A) == self.getParameterValue(self.INPUT_B) fieldList = layerA.fields() writer = self.getOutputFromName(self.OUTPUT).getVectorWriter( fieldList, QgsWkbTypes.multiType(layerA.wkbType()), layerA.crs(), context) spatialIndex = QgsSpatialIndex() splitGeoms = {} request = QgsFeatureRequest() request.setSubsetOfAttributes([]) for aSplitFeature in QgsProcessingUtils.getFeatures( splitLayer, context, request): splitGeoms[aSplitFeature.id()] = aSplitFeature.geometry() spatialIndex.insertFeature(aSplitFeature) # honor the case that user has selection on split layer and has setting "use selection" outFeat = QgsFeature() features = QgsProcessingUtils.getFeatures(layerA, context) if QgsProcessingUtils.featureCount(layerA, context) == 0: total = 100 else: total = 100.0 / QgsProcessingUtils.featureCount(layerA, context) for current, inFeatA in enumerate(features): inGeom = inFeatA.geometry() attrsA = inFeatA.attributes() outFeat.setAttributes(attrsA) if inGeom.isMultipart(): inGeoms = [] for g in inGeom.asGeometryCollection(): inGeoms.append(g) else: inGeoms = [inGeom] lines = spatialIndex.intersects(inGeom.boundingBox()) if len(lines) > 0: # has intersection of bounding boxes splittingLines = [] engine = QgsGeometry.createGeometryEngine(inGeom.geometry()) engine.prepareGeometry() for i in lines: try: splitGeom = splitGeoms[i] except: continue # check if trying to self-intersect if sameLayer: if inFeatA.id() == i: continue if engine.intersects(splitGeom.geometry()): splittingLines.append(splitGeom) if len(splittingLines) > 0: for splitGeom in splittingLines: splitterPList = None outGeoms = [] split_geom_engine = QgsGeometry.createGeometryEngine( splitGeom.geometry()) split_geom_engine.prepareGeometry() while len(inGeoms) > 0: inGeom = inGeoms.pop() if inGeom.isNull( ): # this has been encountered and created a run-time error continue if split_geom_engine.intersects(inGeom.geometry()): inPoints = vector.extractPoints(inGeom) if splitterPList is None: splitterPList = vector.extractPoints( splitGeom) try: result, newGeometries, topoTestPoints = inGeom.splitGeometry( splitterPList, False) except: QgsMessageLog.logMessage( self. tr('Geometry exception while splitting' ), self.tr('Processing'), QgsMessageLog.WARNING) result = 1 # splitGeometry: If there are several intersections # between geometry and splitLine, only the first one is considered. if result == 0: # split occurred if inPoints == vector.extractPoints( inGeom): # bug in splitGeometry: sometimes it returns 0 but # the geometry is unchanged outGeoms.append(inGeom) else: inGeoms.append(inGeom) for aNewGeom in newGeometries: inGeoms.append(aNewGeom) else: outGeoms.append(inGeom) else: outGeoms.append(inGeom) inGeoms = outGeoms parts = [] for aGeom in inGeoms: passed = True if QgsWkbTypes.geometryType( aGeom.wkbType()) == QgsWkbTypes.LineGeometry: numPoints = aGeom.geometry().numPoints() if numPoints <= 2: if numPoints == 2: passed = not aGeom.geometry().isClosed( ) # tests if vertex 0 = vertex 1 else: passed = False # sometimes splitting results in lines of zero length if passed: parts.append(aGeom) if len(parts) > 0: outFeat.setGeometry(QgsGeometry.collectGeometry(parts)) writer.addFeature(outFeat) feedback.setProgress(int(current * total)) del writer
def get_boundary_features_not_covered_by_plots(self, db, plot_layer, boundary_layer, more_bfs_layer, less_layer, error_layer, id_field): """ Return all boundary features that have errors when checking if they are covered by plots. This takes into account both geometric and alphanumeric (topology table) errors. """ dict_uuid_plot = get_uuid_dict(plot_layer, db.names, db.names.T_ID_F) dict_uuid_boundary = get_uuid_dict(boundary_layer, db.names, db.names.T_ID_F) plot_as_lines_layer = processing.run("ladm_col:polygonstolines", { 'INPUT': plot_layer, 'OUTPUT': 'memory:' })['OUTPUT'] # create dict with layer data id_field_idx = plot_as_lines_layer.fields().indexFromName(id_field) request = QgsFeatureRequest().setSubsetOfAttributes([id_field_idx]) dict_plot_as_lines = { feature[id_field]: feature for feature in plot_as_lines_layer.getFeatures(request) } id_field_idx = boundary_layer.fields().indexFromName(id_field) request = QgsFeatureRequest().setSubsetOfAttributes([id_field_idx]) dict_boundary = { feature[id_field]: feature for feature in boundary_layer.getFeatures(request) } exp_more = '"{}" is not null and "{}" is not null'.format( db.names.MORE_BFS_T_LC_BOUNDARY_F, db.names.MORE_BFS_T_LC_PLOT_F) list_more_bfs = [{ 'plot_id': feature[db.names.MORE_BFS_T_LC_PLOT_F], 'boundary_id': feature[db.names.MORE_BFS_T_LC_BOUNDARY_F] } for feature in more_bfs_layer.getFeatures(exp_more)] exp_less = '"{}" is not null and "{}" is not null'.format( db.names.LESS_BFS_T_LC_BOUNDARY_F, db.names.LESS_BFS_T_LC_PLOT_F) list_less = [{ 'plot_id': feature[db.names.LESS_BFS_T_LC_PLOT_F], 'boundary_id': feature[db.names.LESS_BFS_T_LC_BOUNDARY_F] } for feature in less_layer.getFeatures(exp_less)] tmp_inner_rings_layer = self.geometry.get_inner_rings_layer( db.names, plot_layer, db.names.T_ID_F) inner_rings_layer = processing.run( "native:addautoincrementalfield", { 'INPUT': tmp_inner_rings_layer, 'FIELD_NAME': 'AUTO', 'START': 0, 'GROUP_FIELDS': [], 'SORT_EXPRESSION': '', 'SORT_ASCENDING': True, 'SORT_NULLS_FIRST': False, 'OUTPUT': 'memory:' })['OUTPUT'] id_field_idx = inner_rings_layer.fields().indexFromName(id_field) auto_idx = inner_rings_layer.fields().indexFromName('AUTO') request = QgsFeatureRequest().setSubsetOfAttributes( [id_field_idx, auto_idx]) dict_inner_rings = { '{}-{}'.format(feature[id_field], feature['AUTO']): feature for feature in inner_rings_layer.getFeatures(request) } # spatial joins between boundary and inner rings # we use as input layer the rings because it is the existing information # in the terrain polygons and filters better because they are less records spatial_join_inner_rings_boundary_layer = processing.run( "qgis:joinattributesbylocation", { 'INPUT': boundary_layer, 'JOIN': inner_rings_layer, 'PREDICATE': [0], # Intersects 'JOIN_FIELDS': [id_field, 'AUTO'], 'METHOD': 0, 'DISCARD_NONMATCHING': True, 'PREFIX': '', 'OUTPUT': 'memory:' })['OUTPUT'] list_spatial_join_boundary_inner_rings = list() list_spatial_join_boundary_plot_ring = list() for feature in spatial_join_inner_rings_boundary_layer.getFeatures(): # The id field has the same name for both layers # This list is only used to check plot's inner rings without boundaries list_spatial_join_boundary_inner_rings.append({ 'plot_ring_id': '{}-{}'.format(feature[id_field + '_2'], feature['AUTO']), 'boundary_id': feature[id_field] }) # list create for filter inner rings from spatial join with between plot and boundary list_spatial_join_boundary_plot_ring.append({ 'plot_id': feature[id_field + '_2'], 'boundary_id': feature[id_field] }) # Spatial join between boundary and plots as lines spatial_join_boundary_plot_layer = processing.run( "qgis:joinattributesbylocation", { 'INPUT': boundary_layer, 'JOIN': plot_as_lines_layer, 'PREDICATE': [0], 'JOIN_FIELDS': [id_field], 'METHOD': 0, 'DISCARD_NONMATCHING': True, 'PREFIX': '', 'OUTPUT': 'memory:' })['OUTPUT'] # The id field has the same name for both layers list_spatial_join_boundary_plot = [{ 'plot_id': feature[id_field + '_2'], 'boundary_id': feature[id_field] } for feature in spatial_join_boundary_plot_layer.getFeatures()] ##################################################### # Validation of geometric errors ##################################################### # Identify plots with geometry problems and remove coincidence in spatial join between plot as line and boundary # and inner_rings and boundary. If the geometry fails, there is no need to check further topological rules for # plots errors_boundary_plot_diffs = self.geometry.difference_boundary_plot( db.names, boundary_layer, plot_as_lines_layer, db.names.T_ID_F) for error_diff in errors_boundary_plot_diffs: boundary_id = error_diff['id'] # All boundaries with geometric errors are eliminated. It is not necessary check more # in spatial join between boundary and plot as line for item_sj in list_spatial_join_boundary_plot.copy(): if item_sj['boundary_id'] == boundary_id: list_spatial_join_boundary_plot.remove(item_sj) # All boundaries with geometric errors are eliminated. It is not necessary check more # in spatial join between boundary and inner_rings for item_sj in list_spatial_join_boundary_inner_rings.copy(): if item_sj['boundary_id'] == boundary_id: list_spatial_join_boundary_inner_rings.remove(item_sj) ###################################################### # Validation of errors in alphanumeric topology tables ###################################################### # start validation in alphanumeric topology tables for more_bfs # remove spatial join intersection with geometries that no contain lines. Because it is not necessary to check for item_sj in list_spatial_join_boundary_plot.copy(): boundary_id = item_sj['boundary_id'] plot_id = item_sj['plot_id'] if item_sj in list_spatial_join_boundary_plot_ring: # it is removed because it is registered in the spatial join between rings and boundaries # and it shouldn't be registered in the topology table of more_bfs list_spatial_join_boundary_plot.remove(item_sj) else: boundary_geom = dict_boundary[boundary_id].geometry() plot_geom = dict_plot_as_lines[plot_id].geometry() intersection = boundary_geom.intersection(plot_geom) if not intersection.isEmpty(): if intersection.type() != QgsWkbTypes.LineGeometry: if intersection.type() == QgsWkbTypes.UnknownGeometry: has_line = False for part in intersection.asGeometryCollection(): if part.isMultipart(): for i in range(part.numGeometries()): if QgsWkbTypes.geometryType( part.geometryN(i).wkbType() ) == QgsWkbTypes.LineGeometry: has_line = True break else: if part.type() == QgsWkbTypes.LineGeometry: has_line = True break if not has_line: # Remove point intersections plot-boundary list_spatial_join_boundary_plot.remove(item_sj) else: list_spatial_join_boundary_plot.remove(item_sj) # Check relation between plot and boundary not registered in more_bfs errors_not_in_more_bfs = list() errors_duplicate_in_more_bfs = list() for item_sj_bp in list_spatial_join_boundary_plot: count_more_bfs = list_more_bfs.count(item_sj_bp) if count_more_bfs > 1: errors_duplicate_in_more_bfs.append( (item_sj_bp['plot_id'], item_sj_bp['boundary_id'])) elif count_more_bfs == 0: # Check for the special case of two contiguous plots, one of them covers the common boundary, but the # other one does not! This should be still a geometry error but is not captured by the code above. Only # in this point of the whole checks we can validate between the individual boundary and the individual # plot. boundary_geom = dict_boundary[ item_sj_bp['boundary_id']].geometry() plot_geom = dict_plot_as_lines[ item_sj_bp['plot_id']].geometry() intersection = boundary_geom.intersection(plot_geom) if not intersection.isEmpty(): if intersection.isGeosEqual(boundary_geom): errors_not_in_more_bfs.append( (item_sj_bp['plot_id'], item_sj_bp['boundary_id'])) else: errors_boundary_plot_diffs.append({ 'id': item_sj_bp['boundary_id'], 'id_plot': item_sj_bp['plot_id'], 'geometry': boundary_geom }) # finalize validation in more_bfs table # start validation in less table errors_not_in_less = list() errors_duplicate_in_less = list() # start validation in alphanumeric topology tables for less # remove spatial join intersection with geometries that do not contain lines. # Because it is not necessary to check topology register for inner_ring in list_spatial_join_boundary_inner_rings: boundary_id = inner_ring['boundary_id'] plot_ring_id = inner_ring['plot_ring_id'] boundary_geom = dict_boundary[boundary_id].geometry() inner_ring_geom = dict_inner_rings[plot_ring_id].geometry() # check intersections difference to line, we check that collections do not have lines parts intersection = boundary_geom.intersection(inner_ring_geom) has_line = False if not intersection.isEmpty(): if intersection.type() != QgsWkbTypes.LineGeometry: if intersection.type() == QgsWkbTypes.UnknownGeometry: for part in intersection.asGeometryCollection(): if part.isMultipart(): for i in range(part.numGeometries()): if QgsWkbTypes.geometryType( part.geometryN(i).wkbType() ) == QgsWkbTypes.LineGeometry: has_line = True break else: if part.type() == QgsWkbTypes.LineGeometry: has_line = True break else: has_line = True if has_line: tmp_dict_plot_boundary = { 'plot_id': int(plot_ring_id.split('-')[0]), 'boundary_id': boundary_id } count_less = list_less.count(tmp_dict_plot_boundary) if count_less > 1: errors_duplicate_in_less.append( (plot_ring_id, boundary_id)) # duplicate in less table elif count_less == 0: errors_not_in_less.append( (plot_ring_id, boundary_id)) # no registered less table # finalize validation for less table features = list() # boundary not covered by plot for boundary_plot_diff in errors_boundary_plot_diffs: boundary_id = boundary_plot_diff['id'] boundary_geom = boundary_plot_diff['geometry'] plot_id = boundary_plot_diff[ 'id_plot'] if 'id_plot' in boundary_plot_diff else None new_feature = QgsVectorLayerUtils().createFeature( error_layer, boundary_geom, { 0: dict_uuid_boundary.get(boundary_id), 1: dict_uuid_plot.get(plot_id), 2: self.quality_rules_manager.get_error_message( QUALITY_RULE_ERROR_CODE_E200301), 3: QUALITY_RULE_ERROR_CODE_E200301 }) features.append(new_feature) # No registered more bfs if errors_not_in_more_bfs: for error_more_bfs in set(errors_not_in_more_bfs): plot_id = error_more_bfs[0] # plot_id boundary_id = error_more_bfs[1] # boundary_id geom_boundary = dict_boundary[boundary_id].geometry() new_feature = QgsVectorLayerUtils().createFeature( error_layer, geom_boundary, { 0: dict_uuid_boundary.get(boundary_id), 1: dict_uuid_plot.get(plot_id), 2: self.quality_rules_manager.get_error_message( QUALITY_RULE_ERROR_CODE_E200304), 3: QUALITY_RULE_ERROR_CODE_E200304 }) features.append(new_feature) # Duplicate in more bfs if errors_duplicate_in_more_bfs: for error_more_bfs in set(errors_duplicate_in_more_bfs): plot_id = error_more_bfs[0] # plot_id boundary_id = error_more_bfs[1] # boundary_id geom_boundary = dict_boundary[boundary_id].geometry() new_feature = QgsVectorLayerUtils().createFeature( error_layer, geom_boundary, { 0: dict_uuid_boundary.get(boundary_id), 1: dict_uuid_plot.get(plot_id), 2: self.quality_rules_manager.get_error_message( QUALITY_RULE_ERROR_CODE_E200302), 3: QUALITY_RULE_ERROR_CODE_E200302 }) features.append(new_feature) # No registered less if errors_not_in_less: for error_less in set(errors_not_in_less): plot_ring_id = error_less[0] # plot_ring_id plot_id = int(plot_ring_id.split('-')[0]) # plot_id boundary_id = error_less[1] # boundary_id geom_ring = dict_inner_rings[plot_ring_id].geometry() new_feature = QgsVectorLayerUtils().createFeature( error_layer, geom_ring, { 0: dict_uuid_boundary.get(boundary_id), 1: dict_uuid_plot.get(plot_id), 2: self.quality_rules_manager.get_error_message( QUALITY_RULE_ERROR_CODE_E200305), 3: QUALITY_RULE_ERROR_CODE_E200305 }) features.append(new_feature) # Duplicate in less if errors_duplicate_in_less: for error_less in set(errors_duplicate_in_less): plot_ring_id = error_less[0] # plot_ring_id plot_id = int(plot_ring_id.split('-')[0]) # plot_id boundary_id = error_less[1] # boundary_id geom_ring = dict_inner_rings[plot_ring_id].geometry() new_feature = QgsVectorLayerUtils().createFeature( error_layer, geom_ring, { 0: dict_uuid_boundary.get(boundary_id), 1: dict_uuid_plot.get(plot_id), 2: self.quality_rules_manager.get_error_message( QUALITY_RULE_ERROR_CODE_E200303), 3: QUALITY_RULE_ERROR_CODE_E200303 }) features.append(new_feature) return features
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.PrmInputLayer, context) angle = self.parameterAsDouble(parameters, self.PrmTransformRotation, context) scale = self.parameterAsDouble(parameters, self.PrmTransformScale, context) azimuth = self.parameterAsDouble(parameters, self.PrmTransformAzimuth, context) distance = self.parameterAsDouble(parameters, self.PrmTransformDistance, context) units = self.parameterAsInt(parameters, self.PrmTransformUnits, context) to_meters = conversionToMeters(units) distance = distance * to_meters src_crs = source.sourceCrs() wkbtype = source.wkbType() (sink, dest_id) = self.parameterAsSink(parameters, self.PrmOutputLayer, context, source.fields(), wkbtype, src_crs) geom_to_4326 = QgsCoordinateTransform(src_crs, epsg4326, QgsProject.instance()) to_sink_crs = QgsCoordinateTransform(epsg4326, src_crs, QgsProject.instance()) geomtype = QgsWkbTypes.geometryType(wkbtype) featureCount = source.featureCount() total = 100.0 / featureCount if featureCount else 0 iterator = source.getFeatures() for cnt, feature in enumerate(iterator): if feedback.isCanceled(): break geom = feature.geometry() # Find the centroid of the vector shape. We will resize everything based on this centroid = geom.centroid().asPoint() centroid = geom_to_4326.transform(centroid.x(), centroid.y()) cy = centroid.y() cx = centroid.x() if distance != 0: g = geod.Direct(cy, cx, azimuth, distance, Geodesic.LATITUDE | Geodesic.LONGITUDE) new_centroid = QgsPoint(g['lon2'], g['lat2']) else: new_centroid = centroid # Find the x & y coordinates of the new centroid ncy = new_centroid.y() ncx = new_centroid.x() vertices = geom.vertices() for vcnt, vertex in enumerate(vertices): v = geom_to_4326.transform(vertex.x(), vertex.y()) l = geod.Inverse(cy, cx, v.y(), v.x()) vdist = l['s12'] vazi = l['azi1'] if scale != 1: vdist = vdist * scale if angle != 0: vazi += angle g = geod.Direct(ncy, ncx, vazi, vdist, Geodesic.LATITUDE | Geodesic.LONGITUDE) new_vertex = to_sink_crs.transform(g['lon2'], g['lat2']) geom.moveVertex(new_vertex.x(), new_vertex.y(), vcnt) feature.setGeometry(geom) sink.addFeature(feature) if cnt % 100 == 0: feedback.setProgress(int(cnt * total)) return {self.PrmOutputLayer: dest_id}
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT)) fields = source.fields() fields.append(QgsField('vertex_pos', QVariant.Int)) fields.append(QgsField('vertex_index', QVariant.Int)) fields.append(QgsField('vertex_part', QVariant.Int)) if QgsWkbTypes.geometryType(source.wkbType()) == QgsWkbTypes.PolygonGeometry: fields.append(QgsField('vertex_part_ring', QVariant.Int)) fields.append(QgsField('vertex_part_index', QVariant.Int)) fields.append(QgsField('distance', QVariant.Double)) fields.append(QgsField('angle', QVariant.Double)) wkb_type = QgsWkbTypes.Point if QgsWkbTypes.hasM(source.wkbType()): wkb_type = QgsWkbTypes.addM(wkb_type) if QgsWkbTypes.hasZ(source.wkbType()): wkb_type = QgsWkbTypes.addZ(wkb_type) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, wkb_type, source.sourceCrs(), QgsFeatureSink.RegeneratePrimaryKey) if sink is None: raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT)) vertex_indices_string = self.parameterAsString(parameters, self.VERTICES, context) indices = [] for vertex in vertex_indices_string.split(','): try: indices.append(int(vertex)) except: raise QgsProcessingException( self.tr('\'{}\' is not a valid vertex index').format(vertex)) features = source.getFeatures(QgsFeatureRequest(), QgsProcessingFeatureSource.FlagSkipGeometryValidityChecks) total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, f in enumerate(features): if feedback.isCanceled(): break input_geometry = f.geometry() if not input_geometry: sink.addFeature(f, QgsFeatureSink.FastInsert) else: total_vertices = input_geometry.constGet().nCoordinates() for vertex in indices: if vertex < 0: vertex_index = total_vertices + vertex else: vertex_index = vertex if vertex_index < 0 or vertex_index >= total_vertices: continue (success, vertex_id) = input_geometry.vertexIdFromVertexNr(vertex_index) distance = input_geometry.distanceToVertex(vertex_index) angle = math.degrees(input_geometry.angleAtVertex(vertex_index)) output_feature = QgsFeature() attrs = f.attributes() attrs.append(vertex) attrs.append(vertex_index) attrs.append(vertex_id.part) if QgsWkbTypes.geometryType(source.wkbType()) == QgsWkbTypes.PolygonGeometry: attrs.append(vertex_id.ring) attrs.append(vertex_id.vertex) attrs.append(distance) attrs.append(angle) output_feature.setAttributes(attrs) point = input_geometry.vertexAt(vertex_index) output_feature.setGeometry(QgsGeometry(point)) sink.addFeature(output_feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
def make_features_compatible(new_features, input_layer): """Try to make the new features compatible with old features by: - converting single to multi part - dropping additional attributes - adding back M/Z values - drop Z/M - convert multi part to single part :param new_features: new features :type new_features: list of QgsFeatures :param input_layer: input layer :type input_layer: QgsVectorLayer :return: modified features :rtype: list of QgsFeatures """ input_wkb_type = input_layer.wkbType() result_features = [] for new_f in new_features: # Fix attributes if new_f.fields().count() > 0: attributes = [] for field in input_layer.fields(): if new_f.fields().indexFromName(field.name()) >= 0: attributes.append(new_f[field.name()]) else: attributes.append(None) f = QgsFeature(input_layer.fields()) f.setAttributes(attributes) f.setGeometry(new_f.geometry()) new_f = f else: lendiff = len(new_f.attributes()) - len(input_layer.fields()) if lendiff > 0: f = QgsFeature(input_layer.fields()) f.setGeometry(new_f.geometry()) f.setAttributes(new_f.attributes()[:len(input_layer.fields())]) new_f = f elif lendiff < 0: f = QgsFeature(input_layer.fields()) f.setGeometry(new_f.geometry()) attributes = new_f.attributes() + [None for i in range(-lendiff)] f.setAttributes(attributes) new_f = f # Check if we need geometry manipulation new_f_geom_type = QgsWkbTypes.geometryType(new_f.geometry().wkbType()) new_f_has_geom = new_f_geom_type not in (QgsWkbTypes.UnknownGeometry, QgsWkbTypes.NullGeometry) input_layer_has_geom = input_wkb_type not in (QgsWkbTypes.NoGeometry, QgsWkbTypes.Unknown) # Drop geometry if layer is geometry-less if not input_layer_has_geom and new_f_has_geom: f = QgsFeature(input_layer.fields()) f.setAttributes(new_f.attributes()) new_f = f result_features.append(new_f) continue # skip the rest if input_layer_has_geom and new_f_has_geom and \ new_f.geometry().wkbType() != input_wkb_type: # Fix geometry # Single -> Multi if (QgsWkbTypes.isMultiType(input_wkb_type) and not new_f.geometry().isMultipart()): new_geom = new_f.geometry() new_geom.convertToMultiType() new_f.setGeometry(new_geom) # Drop Z/M if (new_f.geometry().constGet().is3D() and not QgsWkbTypes.hasZ(input_wkb_type)): new_geom = new_f.geometry() new_geom.get().dropZValue() new_f.setGeometry(new_geom) if (new_f.geometry().constGet().isMeasure() and not QgsWkbTypes.hasM(input_wkb_type)): new_geom = new_f.geometry() new_geom.get().dropMValue() new_f.setGeometry(new_geom) # Add Z/M back (set it to 0) if (not new_f.geometry().constGet().is3D() and QgsWkbTypes.hasZ(input_wkb_type)): new_geom = new_f.geometry() new_geom.get().addZValue(0.0) new_f.setGeometry(new_geom) if (not new_f.geometry().constGet().isMeasure() and QgsWkbTypes.hasM(input_wkb_type)): new_geom = new_f.geometry() new_geom.get().addMValue(0.0) new_f.setGeometry(new_geom) # Multi -> Single if (not QgsWkbTypes.isMultiType(input_wkb_type) and new_f.geometry().isMultipart()): g = new_f.geometry() g2 = g.constGet() for i in range(g2.partCount()): # Clone or crash! g4 = QgsGeometry(g2.geometryN(i).clone()) f = QgsVectorLayerUtils.createFeature(input_layer, g4, {i: new_f.attribute(i) for i in range(new_f.fields().count())}) result_features.append(f) else: result_features.append(new_f) else: result_features.append(new_f) return result_features
def processAlgorithm(self, progress): layerA = dataobjects.getObjectFromUri(self.getParameterValue(self.INPUT_A)) splitLayer = dataobjects.getObjectFromUri(self.getParameterValue(self.INPUT_B)) sameLayer = self.getParameterValue(self.INPUT_A) == self.getParameterValue(self.INPUT_B) fieldList = layerA.fields() writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(fieldList, layerA.wkbType(), layerA.crs()) spatialIndex = QgsSpatialIndex() splitGeoms = {} request = QgsFeatureRequest() request.setSubsetOfAttributes([]) for aSplitFeature in vector.features(splitLayer, request): splitGeoms[aSplitFeature.id()] = aSplitFeature.geometry() spatialIndex.insertFeature(aSplitFeature) # honor the case that user has selection on split layer and has setting "use selection" outFeat = QgsFeature() features = vector.features(layerA) if len(features) == 0: total = 100 else: total = 100.0 / float(len(features)) multiGeoms = 0 # how many multi geometries were encountered for current, inFeatA in enumerate(features): inGeom = inFeatA.geometry() if inGeom.isMultipart(): multiGeoms += 1 # MultiGeometries are not allowed because the result of a splitted part cannot be clearly defined: # 1) add both new parts as new features # 2) store one part as a new feature and the other one as part of the multi geometry # 2a) which part should be which, seems arbitrary else: attrsA = inFeatA.attributes() outFeat.setAttributes(attrsA) inGeoms = [inGeom] lines = spatialIndex.intersects(inGeom.boundingBox()) if len(lines) > 0: # has intersection of bounding boxes splittingLines = [] engine = QgsGeometry.createGeometryEngine(inGeom.geometry()) engine.prepareGeometry() for i in lines: try: splitGeom = splitGeoms[i] except: continue # check if trying to self-intersect if sameLayer: if inFeatA.id() == i: continue if engine.intersects(splitGeom.geometry()): splittingLines.append(splitGeom) if len(splittingLines) > 0: for splitGeom in splittingLines: splitterPList = None outGeoms = [] split_geom_engine = QgsGeometry.createGeometryEngine(splitGeom.geometry()) split_geom_engine.prepareGeometry() while len(inGeoms) > 0: inGeom = inGeoms.pop() if split_geom_engine.intersects(inGeom.geometry()): inPoints = vector.extractPoints(inGeom) if splitterPList == None: splitterPList = vector.extractPoints(splitGeom) try: result, newGeometries, topoTestPoints = inGeom.splitGeometry(splitterPList, False) except: ProcessingLog.addToLog(ProcessingLog.LOG_WARNING, self.tr('Geometry exception while splitting')) result = 1 # splitGeometry: If there are several intersections # between geometry and splitLine, only the first one is considered. if result == 0: # split occurred if inPoints == vector.extractPoints(inGeom): # bug in splitGeometry: sometimes it returns 0 but # the geometry is unchanged QgsMessageLog.logMessage("appending") outGeoms.append(inGeom) else: inGeoms.append(inGeom) for aNewGeom in newGeometries: inGeoms.append(aNewGeom) else: QgsMessageLog.logMessage("appending else") outGeoms.append(inGeom) else: outGeoms.append(inGeom) inGeoms = outGeoms for aGeom in inGeoms: passed = True if QgsWkbTypes.geometryType( aGeom.wkbType() ) == QgsWkbTypes.LineGeometry \ and not QgsWkbTypes.isMultiType(aGeom.wkbType()): passed = len(aGeom.asPolyline()) > 2 if not passed: passed = (len(aGeom.asPolyline()) == 2 and aGeom.asPolyline()[0] != aGeom.asPolyline()[1]) # sometimes splitting results in lines of zero length if passed: outFeat.setGeometry(aGeom) writer.addFeature(outFeat) progress.setPercentage(int(current * total)) if multiGeoms > 0: ProcessingLog.addToLog(ProcessingLog.LOG_INFO, self.tr('Feature geometry error: %s input features ignored due to multi-geometry.') % str(multiGeoms)) del writer
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) line_source = self.parameterAsSource(parameters, self.LINES, context) sameLayer = parameters[self.INPUT] == parameters[self.LINES] (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, source.fields(), QgsWkbTypes.multiType(source.wkbType()), source.sourceCrs()) spatialIndex = QgsSpatialIndex() splitGeoms = {} request = QgsFeatureRequest() request.setSubsetOfAttributes([]) request.setDestinationCrs(source.sourceCrs()) for aSplitFeature in line_source.getFeatures(request): if feedback.isCanceled(): break splitGeoms[aSplitFeature.id()] = aSplitFeature.geometry() spatialIndex.insertFeature(aSplitFeature) # honor the case that user has selection on split layer and has setting "use selection" outFeat = QgsFeature() features = source.getFeatures() total = 100.0 / source.featureCount() if source.featureCount() else 100 for current, inFeatA in enumerate(features): if feedback.isCanceled(): break inGeom = inFeatA.geometry() attrsA = inFeatA.attributes() outFeat.setAttributes(attrsA) if inGeom.isMultipart(): inGeoms = [] for g in inGeom.asGeometryCollection(): inGeoms.append(g) else: inGeoms = [inGeom] lines = spatialIndex.intersects(inGeom.boundingBox()) if len(lines) > 0: # has intersection of bounding boxes splittingLines = [] engine = QgsGeometry.createGeometryEngine(inGeom.geometry()) engine.prepareGeometry() for i in lines: try: splitGeom = splitGeoms[i] except: continue # check if trying to self-intersect if sameLayer: if inFeatA.id() == i: continue if engine.intersects(splitGeom.geometry()): splittingLines.append(splitGeom) if len(splittingLines) > 0: for splitGeom in splittingLines: splitterPList = None outGeoms = [] split_geom_engine = QgsGeometry.createGeometryEngine(splitGeom.geometry()) split_geom_engine.prepareGeometry() while len(inGeoms) > 0: if feedback.isCanceled(): break inGeom = inGeoms.pop() if inGeom.isNull(): # this has been encountered and created a run-time error continue if split_geom_engine.intersects(inGeom.geometry()): inPoints = vector.extractPoints(inGeom) if splitterPList is None: splitterPList = vector.extractPoints(splitGeom) try: result, newGeometries, topoTestPoints = inGeom.splitGeometry(splitterPList, False) except: feedback.reportError(self.tr('Geometry exception while splitting')) result = 1 # splitGeometry: If there are several intersections # between geometry and splitLine, only the first one is considered. if result == 0: # split occurred if inPoints == vector.extractPoints(inGeom): # bug in splitGeometry: sometimes it returns 0 but # the geometry is unchanged outGeoms.append(inGeom) else: inGeoms.append(inGeom) for aNewGeom in newGeometries: inGeoms.append(aNewGeom) else: outGeoms.append(inGeom) else: outGeoms.append(inGeom) inGeoms = outGeoms parts = [] for aGeom in inGeoms: if feedback.isCanceled(): break passed = True if QgsWkbTypes.geometryType(aGeom.wkbType()) == QgsWkbTypes.LineGeometry: numPoints = aGeom.geometry().numPoints() if numPoints <= 2: if numPoints == 2: passed = not aGeom.geometry().isClosed() # tests if vertex 0 = vertex 1 else: passed = False # sometimes splitting results in lines of zero length if passed: parts.append(aGeom) if len(parts) > 0: outFeat.setGeometry(QgsGeometry.collectGeometry(parts)) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) method = self.parameterAsEnum(parameters, self.METHOD, context) wkb_type = source.wkbType() fields = source.fields() if QgsWkbTypes.geometryType(wkb_type) == QgsWkbTypes.PolygonGeometry: areaName = vector.createUniqueFieldName('area', fields) fields.append(QgsField(areaName, QVariant.Double)) perimeterName = vector.createUniqueFieldName('perimeter', fields) fields.append(QgsField(perimeterName, QVariant.Double)) elif QgsWkbTypes.geometryType(wkb_type) == QgsWkbTypes.LineGeometry: lengthName = vector.createUniqueFieldName('length', fields) fields.append(QgsField(lengthName, QVariant.Double)) else: xName = vector.createUniqueFieldName('xcoord', fields) fields.append(QgsField(xName, QVariant.Double)) yName = vector.createUniqueFieldName('ycoord', fields) fields.append(QgsField(yName, QVariant.Double)) if QgsWkbTypes.hasZ(source.wkbType()): self.export_z = True zName = vector.createUniqueFieldName('zcoord', fields) fields.append(QgsField(zName, QVariant.Double)) if QgsWkbTypes.hasM(source.wkbType()): self.export_m = True zName = vector.createUniqueFieldName('mvalue', fields) fields.append(QgsField(zName, QVariant.Double)) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, wkb_type, source.sourceCrs()) coordTransform = None # Calculate with: # 0 - layer CRS # 1 - project CRS # 2 - ellipsoidal self.distance_area = QgsDistanceArea() if method == 2: self.distance_area.setSourceCrs(source.sourceCrs()) self.distance_area.setEllipsoid(context.project().ellipsoid()) elif method == 1: coordTransform = QgsCoordinateTransform(source.sourceCrs(), context.project().crs()) features = source.getFeatures() total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, f in enumerate(features): if feedback.isCanceled(): break outFeat = f attrs = f.attributes() inGeom = f.geometry() if inGeom: if coordTransform is not None: inGeom.transform(coordTransform) if inGeom.type() == QgsWkbTypes.PointGeometry: attrs.extend(self.point_attributes(inGeom)) elif inGeom.type() == QgsWkbTypes.PolygonGeometry: attrs.extend(self.polygon_attributes(inGeom)) else: attrs.extend(self.line_attributes(inGeom)) outFeat.setAttributes(attrs) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.PrmInputLayer, context) measureTotal = self.parameterAsBool(parameters, self.PrmMeasureTotalLength, context) units = self.parameterAsInt(parameters, self.PrmUnitsOfMeasure, context) autoStyle = self.parameterAsBool(parameters, self.PrmAutomaticStyline, context) srcCRS = source.sourceCrs() f = QgsFields() f.append(QgsField("label", QVariant.String)) f.append(QgsField("distance", QVariant.Double)) f.append(QgsField("units", QVariant.String)) if not measureTotal: f.append(QgsField("heading_to", QVariant.Double)) f.append(QgsField("total_distance", QVariant.Double)) (sink, dest_id) = self.parameterAsSink( parameters, self.PrmOutputLayer, context, f, QgsWkbTypes.LineString, srcCRS) if srcCRS != epsg4326: geomTo4326 = QgsCoordinateTransform(srcCRS, epsg4326, QgsProject.instance()) toSinkCrs = QgsCoordinateTransform(epsg4326, srcCRS, QgsProject.instance()) wkbtype = source.wkbType() geomtype = QgsWkbTypes.geometryType(wkbtype) featureCount = source.featureCount() total = 100.0 / featureCount if featureCount else 0 iterator = source.getFeatures() for cnt, feature in enumerate(iterator): if feedback.isCanceled(): break if geomtype == QgsWkbTypes.LineGeometry: if feature.geometry().isMultipart(): ptdata = [feature.geometry().asMultiPolyline()] else: ptdata = [[feature.geometry().asPolyline()]] else: #polygon if feature.geometry().isMultipart(): ptdata = feature.geometry().asMultiPolygon() else: ptdata = [feature.geometry().asPolygon()] if len(ptdata) < 1: continue for seg in ptdata: if len(seg) < 1: continue if measureTotal: for pts in seg: numpoints = len(pts) if numpoints < 2: continue f = QgsFeature() f.setGeometry(QgsGeometry.fromPolylineXY(pts)) ptStart = QgsPointXY(pts[0].x(), pts[0].y()) if srcCRS != epsg4326: # Convert to 4326 ptStart = geomTo4326.transform(ptStart) # Calculate the total distance of this line segment distance = 0.0 for x in range(1,numpoints): ptEnd = QgsPointXY(pts[x].x(), pts[x].y()) if srcCRS != epsg4326: # Convert to 4326 ptEnd = geomTo4326.transform(ptEnd) l = geod.Inverse(ptStart.y(), ptStart.x(), ptEnd.y(), ptEnd.x()) distance += l['s12'] ptStart = ptEnd distance = self.unitDistance(units, distance) # Distance converted to the selected unit of measure attr = ["{:.2f} {}".format(distance, unitsAbbr[units]), distance, unitsAbbr[units] ] f.setAttributes(attr) sink.addFeature(f) else: for pts in seg: numpoints = len(pts) if numpoints < 2: continue ptStart = QgsPointXY(pts[0].x(), pts[0].y()) if srcCRS != epsg4326: # Convert to 4326 ptStart = geomTo4326.transform(ptStart) # Calculate the total distance of this line segment totalDistance = 0.0 for x in range(1,numpoints): ptEnd = QgsPointXY(pts[x].x(), pts[x].y()) if srcCRS != epsg4326: # Convert to 4326 ptEnd = geomTo4326.transform(ptEnd) l = geod.Inverse(ptStart.y(), ptStart.x(), ptEnd.y(), ptEnd.x()) totalDistance += l['s12'] ptStart = ptEnd totalDistance = self.unitDistance(units, totalDistance) # Distance converted to the selected unit of measure ptStart = QgsPointXY(pts[0].x(), pts[0].y()) if srcCRS != epsg4326: # Convert to 4326 pt1 = geomTo4326.transform(ptStart) else: pt1 = ptStart for x in range(1,numpoints): ptEnd = QgsPointXY(pts[x].x(), pts[x].y()) f = QgsFeature() f.setGeometry(QgsGeometry.fromPolylineXY([ptStart, ptEnd])) if srcCRS != epsg4326: # Convert to 4326 pt2 = geomTo4326.transform(ptEnd) else: pt2 = ptEnd l = geod.Inverse(pt1.y(), pt1.x(), pt2.y(), pt2.x()) ptStart = ptEnd pt1 = pt2 distance = self.unitDistance(units, l['s12']) attr = ["{:.2f} {}".format(distance, unitsAbbr[units]), distance, unitsAbbr[units], l['azi1'],totalDistance ] f.setAttributes(attr) sink.addFeature(f) if cnt % 100 == 0: feedback.setProgress(int(cnt * total)) if autoStyle and context.willLoadLayerOnCompletion(dest_id): context.layerToLoadOnCompletionDetails(dest_id).setPostProcessor(StylePostProcessor.create()) return {self.PrmOutputLayer: dest_id}
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.PrmInputLayer, context) preserve_final_pt = self.parameterAsBool(parameters, self.PrmPreserveFinalPoint, context) min_distance = self.parameterAsDouble(parameters, self.PrmMinDistance, context) units = self.parameterAsInt(parameters, self.PrmUnitsOfMeasure, context) # Get the minimum distance in meters min_distance = min_distance * conversionToMeters(units) wkbtype = source.wkbType() num_bad = 0 if QgsWkbTypes.geometryType(wkbtype) != QgsWkbTypes.LineGeometry: feedback.reportError(tr("Please select a valid line layer.")) return({}) layercrs = source.sourceCrs() (sink, dest_id) = self.parameterAsSink( parameters, self.PrmOutputLayer, context, source.fields(), wkbtype, layercrs) if layercrs != epsg4326: transto4326 = QgsCoordinateTransform(layercrs, epsg4326, QgsProject.instance()) transfrom4326 = QgsCoordinateTransform(epsg4326, layercrs, QgsProject.instance()) total = 100.0 / source.featureCount() if source.featureCount() else 0 iterator = source.getFeatures() num_bad = 0 for cnt, feature in enumerate(iterator): if feedback.isCanceled(): break geom = feature.geometry() # Force the geometry to be in degrees so that we can use the geodesic algorithms if layercrs != epsg4326: results = geom.transform(transto4326) num_parts = geom.constGet().partCount() # feedback.pushInfo('num_parts {}'.format(num_parts)) try: fline = QgsFeature() fgeom = QgsGeometry() is_valid = False for part in geom.constGet().parts(): vertex_cnt = part.vertexCount() # feedback.pushInfo('vertex_cnt {}'.format(vertex_cnt)) if vertex_cnt <= 1: # line of 1 or less points is invalid continue pts = [] for vcnt, vertex in enumerate(part.vertices()): if vcnt == 0: # This is the first point so we will save it pts.append(vertex) ptLast = vertex else: ptNext = vertex gline = geod.InverseLine(ptLast.y(), ptLast.x(), ptNext.y(), ptNext.x()) if gline.s13 >= min_distance: pts.append(ptNext) ptLast = ptNext elif (vcnt == vertex_cnt - 1) and preserve_final_pt: if len(pts) >= 2: # We have two or more points already on our pts list. Check to see if # the next to last entry distance and the end point are greater than the # the minimum distance. If so replace the last entry with this one else # because the user has selected to preserve the last end point we will # accept the end point. gline = geod.InverseLine(pts[-2].y(), pts[-2].x(), ptNext.y(), ptNext.x()) if gline.s13 >= min_distance: pts[-1] = ptNext else: pts.append(ptNext) elif len(pts) == 1: # We have only the beginning point of the line and because the user has # selected that the last point to be preserved we will allow a distance less # than the minimum so that the line can be preserved. pts.append(ptNext) if len(pts) > 1: # There must be more than one point to make a valid line fgeom.addPoints(pts, QgsWkbTypes.LineGeometry) is_valid = True if is_valid: # Only save the feature if it is valid fline.setAttributes(feature.attributes()) if layercrs != epsg4326: fgeom.transform(transfrom4326) fline.setGeometry(fgeom) sink.addFeature(fline) else: num_bad += 1 except Exception: '''s = traceback.format_exc() feedback.pushInfo(s)''' num_bad += 1 feedback.setProgress(int(cnt * total)) if num_bad > 0: feedback.pushInfo(tr("{} out of {} features from input layer were invalid and were skipped.".format(num_bad, source.featureCount()))) return {self.PrmOutputLayer: dest_id}
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT)) # Unit: # 0 - degree # 1 - radian # 2 - grade unit = self.parameterAsEnum(parameters, self.UNIT, context) # Interval: # 0 - [0 ; Pi[ # 1 - [0 ; Pi/2[ interval = self.parameterAsEnum(parameters, self.INTERVAL, context) # Calculate with projection: # 0 - layer CRS # 1 - project CRS # 2 - ellipsoidal (TODO: NOT USED AT THE MOMENT) method = self.parameterAsEnum(parameters, self.METHOD, context) rounded = self.parameterAsBoolean(parameters, self.ROUNDED, context) classification = self.parameterAsBoolean(parameters, self.CLASSIFICATION, context) classification_step = self.parameterAsDouble(parameters, self.CLASSIFICATION_STEP, context) histogram = self.parameterAsBoolean(parameters, self.HISTOGRAM, context) histogram_step = self.parameterAsDouble(parameters, self.HISTOGRAM_STEP, context) wkb_type = source.wkbType() # TODO ? improvement: test if the layer contains at least one segment if QgsWkbTypes.geometryType(wkb_type) != QgsWkbTypes.LineGeometry: # TODO IMPROVE FEEDBACK feedback.reportError( 'The layer geometry type is different from a line') return {} fields = source.fields() new_fields = QgsFields() new_fields.append(QgsField('orientation', QVariant.Double)) if classification: new_fields.append(QgsField('classification', QVariant.Double)) fields = QgsProcessingUtils.combineFields(fields, new_fields) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, wkb_type, source.sourceCrs()) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) coord_transform = None self.distance_area = QgsDistanceArea() # if method == 2: # self.distance_area.setSourceCrs(source.sourceCrs(), context.transformContext()) # self.distance_area.setEllipsoid(context.ellipsoid()) if method == 1: if not context.project(): raise QgsProcessingException( self.tr('No project is available in this context')) coord_transform = QgsCoordinateTransform(source.sourceCrs(), context.project().crs(), context.project()) features = source.getFeatures() total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, f in enumerate(features): if feedback.isCanceled(): break out_feature = f attrs = f.attributes() geom = f.geometry() if geom: if coord_transform is not None: geom.transform(coord_transform) orientation = angle(geom, unit, interval, rounded) if orientation is not None: if classification: class_int = int(orientation / classification_step) attrs.extend([orientation, class_int]) else: attrs.extend([orientation]) # ensure consistent count of attributes - otherwise null # geometry features will have incorrect attribute length # and provider may reject them if len(attrs) < len(fields): attrs += [NULL] * (len(fields) - len(attrs)) out_feature.setAttributes(attrs) sink.addFeature(out_feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
def _group_geom_name(self, geom_str): geom = QgsWkbTypes.geometryDisplayString( QgsWkbTypes.geometryType( QgsWkbTypes.parseType( geom_str))) if geom_str else self.NO_GEOM return geom
def drop_z_from_geom(cls, geom: QgsGeometry, geom_type: QgsWkbTypes): target_type = cls._get_non_z_geom_type(geom_type) type_to_convert = QgsWkbTypes.geometryType(target_type) return geom.convertToType(type_to_convert), target_type
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) method = self.parameterAsEnum(parameters, self.METHOD, context) wkb_type = source.wkbType() fields = source.fields() new_fields = QgsFields() if QgsWkbTypes.geometryType(wkb_type) == QgsWkbTypes.PolygonGeometry: new_fields.append(QgsField('area', QVariant.Double)) new_fields.append(QgsField('perimeter', QVariant.Double)) elif QgsWkbTypes.geometryType(wkb_type) == QgsWkbTypes.LineGeometry: new_fields.append(QgsField('length', QVariant.Double)) else: new_fields.append(QgsField('xcoord', QVariant.Double)) new_fields.append(QgsField('ycoord', QVariant.Double)) if QgsWkbTypes.hasZ(source.wkbType()): self.export_z = True new_fields.append(QgsField('zcoord', QVariant.Double)) if QgsWkbTypes.hasM(source.wkbType()): self.export_m = True new_fields.append(QgsField('mvalue', QVariant.Double)) fields = QgsProcessingUtils.combineFields(fields, new_fields) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, wkb_type, source.sourceCrs()) coordTransform = None # Calculate with: # 0 - layer CRS # 1 - project CRS # 2 - ellipsoidal self.distance_area = QgsDistanceArea() if method == 2: self.distance_area.setSourceCrs(source.sourceCrs(), context.transformContext()) self.distance_area.setEllipsoid(context.project().ellipsoid()) elif method == 1: coordTransform = QgsCoordinateTransform(source.sourceCrs(), context.project().crs(), context.project()) features = source.getFeatures() total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, f in enumerate(features): if feedback.isCanceled(): break outFeat = f attrs = f.attributes() inGeom = f.geometry() if inGeom: if coordTransform is not None: inGeom.transform(coordTransform) if inGeom.type() == QgsWkbTypes.PointGeometry: attrs.extend(self.point_attributes(inGeom)) elif inGeom.type() == QgsWkbTypes.PolygonGeometry: attrs.extend(self.polygon_attributes(inGeom)) else: attrs.extend(self.line_attributes(inGeom)) outFeat.setAttributes(attrs) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
def requestFlightPathLayer(self): # https://docs.qgis.org/testing/en/docs/pyqgis_developer_cookbook/vector.html#memory-provider filmsBySourceType = self.getSelection() epsg = 4326 vectorCrs = QgsCoordinateReferenceSystem( epsg, QgsCoordinateReferenceSystem.EpsgCrsId) flightPathPointLayer = QgsVectorLayer( "MultiPoint?crs=epsg:{0}".format(epsg), "FlugwegePunkt", "memory") flightPathLineLayer = QgsVectorLayer( "MultiLineString?crs=epsg:{0}".format(epsg), "FlugwegeLinie", "memory") flightPathPointLayer.setCrs(vectorCrs) flightPathPointLayer.setCrs(vectorCrs) flightPathPointProvider = flightPathPointLayer.dataProvider() flightPathLineProvider = flightPathLineLayer.dataProvider() # add fields fields = [ QgsField("filmnummer", QVariant.String), QgsField("flugweg_quelle", QVariant.String), QgsField("flugdatum", QVariant.String), QgsField("fotograf", QVariant.String), QgsField("pilot", QVariant.String), QgsField("flugzeug", QVariant.String), QgsField("abflug_zeit", QVariant.String), QgsField("ankunft_zeit", QVariant.String), QgsField("flugzeit", QVariant.String), QgsField("abflug_flughafen", QVariant.String), QgsField("ankunft_flughafen", QVariant.String), QgsField("wetter", QVariant.String), QgsField("target", QVariant.String), QgsField("orientation", QVariant.String) ] flightPathPointProvider.addAttributes(fields) flightPathLineProvider.addAttributes(fields) flightPathPointLayer.updateFields( ) # tell the vector layer to fetch changes from the provider flightPathLineLayer.updateFields() pointFeatureList = [] lineFeatureList = [] # Point: Flight GPS for filmNumber in filmsBySourceType[0]: # load .shp file, get geometries as multigeometry pointFeatureList.append( self.getFeatureWithMultiGeomFromOgrShp(filmNumber, ".shp", ogr.wkbMultiPoint, "flight_gps", vectorCrs)) # Point: Camera GPS for filmNumber in filmsBySourceType[2]: # load _gps.shp file, get geometries as multigeometry pointFeatureList.append( self.getFeatureWithMultiGeomFromOgrShp(filmNumber, "_gps.shp", ogr.wkbMultiPoint, "camera_gps", vectorCrs)) # Point: Image Mapping for filmNumber in filmsBySourceType[4]: # load image_cp based on orientation from APIS db pointFeatureList.append( self.getFeatureWithMultiGeomFromSpatialite( filmNumber, QgsWkbTypes.geometryType(QgsWkbTypes.MultiPoint), "image_mapping")) # Line: Flight GPS for filmNumber in filmsBySourceType[1]: # load _lin.shp file, get geometries as multigeometry lineFeatureList.append( self.getFeatureWithMultiGeomFromOgrShp(filmNumber, "_lin.shp", ogr.wkbMultiLineString, "flight_gps", vectorCrs)) # Line: Camera GPS for filmNumber in filmsBySourceType[3]: # load _gps.shp file create line with Points2Path, get geometry as multigeometry lineFeatureList.append( self.multiPointToLineString( self.getFeatureWithMultiGeomFromOgrShp(filmNumber, "_gps.shp", ogr.wkbMultiPoint, "camera_gps", vectorCrs, sortBy='bildnr'))) # Line: Image Mapping for filmNumber in filmsBySourceType[5]: # load image_cp based on orientation from APIS db create line with Points2Path, get geometry as multigeometry lineFeatureList.append( self.multiPointToLineString( self.getFeatureWithMultiGeomFromSpatialite( filmNumber, QgsWkbTypes.geometryType(QgsWkbTypes.MultiPoint), "image_mapping"))) flightPathPointProvider.addFeatures( [pF for pF in pointFeatureList if pF is not None]) flightPathLineProvider.addFeatures( [lF for lF in lineFeatureList if lF is not None]) # update layer's extent when new features have been added # because change of extent in provider is not propagated to the layer flightPathPointLayer.updateExtents() flightPathLineLayer.updateExtents() return flightPathPointLayer, flightPathLineLayer
def processAlgorithm(self, parameters, context, feedback): sourceA = self.parameterAsSource(parameters, self.INPUT, context) sourceB = self.parameterAsSource(parameters, self.OVERLAY, context) geomType = QgsWkbTypes.multiType(sourceA.wkbType()) fieldsA = self.parameterAsFields(parameters, self.INPUT_FIELDS, context) fieldsB = self.parameterAsFields(parameters, self.OVERLAY_FIELDS, context) fieldListA = QgsFields() field_indices_a = [] if len(fieldsA) > 0: for f in fieldsA: idxA = sourceA.fields().lookupField(f) if idxA >= 0: field_indices_a.append(idxA) fieldListA.append(sourceA.fields()[idxA]) else: fieldListA = sourceA.fields() field_indices_a = [i for i in range(0, fieldListA.count())] fieldListB = QgsFields() field_indices_b = [] if len(fieldsB) > 0: for f in fieldsB: idxB = sourceB.fields().lookupField(f) if idxB >= 0: field_indices_b.append(idxB) fieldListB.append(sourceB.fields()[idxB]) else: fieldListB = sourceB.fields() field_indices_b = [i for i in range(0, fieldListB.count())] output_fields = QgsProcessingUtils.combineFields( fieldListA, fieldListB) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, output_fields, geomType, sourceA.sourceCrs()) outFeat = QgsFeature() indexB = QgsSpatialIndex( sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes( []).setDestinationCrs(sourceA.sourceCrs(), context.transformContext())), feedback) total = 100.0 / sourceA.featureCount() if sourceA.featureCount() else 1 count = 0 for featA in sourceA.getFeatures( QgsFeatureRequest().setSubsetOfAttributes(field_indices_a)): if feedback.isCanceled(): break if not featA.hasGeometry(): continue geom = featA.geometry() atMapA = featA.attributes() intersects = indexB.intersects(geom.boundingBox()) request = QgsFeatureRequest().setFilterFids(intersects) request.setDestinationCrs(sourceA.sourceCrs(), context.transformContext()) request.setSubsetOfAttributes(field_indices_b) engine = None if len(intersects) > 0: # use prepared geometries for faster intersection tests engine = QgsGeometry.createGeometryEngine(geom.constGet()) engine.prepareGeometry() for featB in sourceB.getFeatures(request): if feedback.isCanceled(): break tmpGeom = featB.geometry() if engine.intersects(tmpGeom.constGet()): out_attributes = [ featA.attributes()[i] for i in field_indices_a ] out_attributes.extend( [featB.attributes()[i] for i in field_indices_b]) int_geom = QgsGeometry(geom.intersection(tmpGeom)) if int_geom.wkbType( ) == QgsWkbTypes.Unknown or QgsWkbTypes.flatType( int_geom.wkbType( )) == QgsWkbTypes.GeometryCollection: int_com = geom.combine(tmpGeom) int_geom = QgsGeometry() if int_com: int_sym = geom.symDifference(tmpGeom) int_geom = QgsGeometry(int_com.difference(int_sym)) if int_geom.isEmpty() or not int_geom.isGeosValid(): raise QgsProcessingException( self.tr('GEOS geoprocessing error: One or ' 'more input features have invalid ' 'geometry.')) try: if QgsWkbTypes.geometryType(int_geom.wkbType( )) == QgsWkbTypes.geometryType(geomType): int_geom.convertToMultiType() outFeat.setGeometry(int_geom) outFeat.setAttributes(out_attributes) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: raise QgsProcessingException( self.tr('Feature geometry error: One or more ' 'output features ignored due to invalid ' 'geometry.')) count += 1 feedback.setProgress(int(count * total)) return {self.OUTPUT: dest_id}
def processAlgorithm(self, parameters, context, feedback): ''' Here is where the processing itself takes place. ''' # if not is_dependencies_satisfied: return {} # Init # The number of features in the input layer could be trimmed to user selection. the_layer = self.parameterAsSource(parameters, self.THE_LAYER, context) gok = QgsWkbTypes.geometryType( the_layer.wkbType()) == QgsWkbTypes.PointGeometry if the_layer is None or not gok: raise QgsProcessingException( self.invalidSourceError(parameters, self.THE_LAYER)) # bCHscal = self.parameterAsBool(parameters, self.BSCALE, context) if bCHscal: # Use another channel for scaling. All data from that channel will be used. scally = self.parameterAsSource(parameters, self.SCALLY, context) if scally is None or the_layer.wkbType() != QgsWkbTypes.Point: raise QgsProcessingException( self.invalidSourceError(parameters, self.SCALLY)) fidu_fld = self.parameterAsString(parameters, self.FID_FLD, context) data_fld = self.parameterAsString(parameters, self.DATA_FLD, context) line_fld = self.parameterAsString(parameters, self.LINE_FLD, context) invP = self.parameterAsBool(parameters, self.INVERTP, context) dumval = self.parameterAsDouble(parameters, self.DUMVAL, context) scale = self.parameterAsDouble(parameters, self.SCALE, context) offset = self.parameterAsDouble(parameters, self.OFFSET, context) join_to_line = self.parameterAsBool(parameters, self.JOINL, context) data = the_layer.fields().at(the_layer.fields().lookupField(data_fld)) fidu = the_layer.fields().at(the_layer.fields().lookupField(fidu_fld)) if not data.isNumeric() or not fidu.isNumeric(): raise QgsProcessingException( self.invalidSourceError(parameters, self.THE_LAYER)) line = the_layer.fields().at(the_layer.fields().lookupField(line_fld)) data_ix = the_layer.fields().lookupField(data_fld) line_ix = the_layer.fields().lookupField(line_fld) fidu_ix = the_layer.fields().lookupField(fidu_fld) # Set output vector layer: point(X, Y, M) M is data value at that point output_wkb = QgsWkbTypes.LineString output_wkb = QgsWkbTypes.addM(output_wkb) # Fields of stacked profiles vector line_def = the_layer.fields().at(line_ix) fields = QgsFields() if line_def is not None: fields = QgsFields() fields.append(QgsField('Line', QVariant.String, '', 16)) fields.append(QgsField('Type', QVariant.String, '', 2)) fields.append(QgsField('NbPts', QVariant.Int, '', 10, 0)) fields.append(QgsField('Azimuth', QVariant.Double, '', 10, 6)) fields.append(QgsField('DistEP', QVariant.Double, '', 10, 2)) fields.append(QgsField('Length', QVariant.Double, '', 10, 2)) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, output_wkb, the_layer.sourceCrs()) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) # Get the features and fields of interest features = the_layer.getFeatures( QgsFeatureRequest().setSubsetOfAttributes( [fidu_ix, line_ix, data_ix]), QgsProcessingFeatureSource.FlagSkipGeometryValidityChecks) # CSV # Find min/max of data values for all lines and save each line in a csv file # Then process each line separately: can have any number of lines... lines = [] xyzf = [] lineN = '' nL = 0 TL = 0. total = 60.0 / the_layer.featureCount() if the_layer.featureCount( ) else 0 stat = QgsStatisticalSummary() for current, ft in enumerate(features): if feedback.isCanceled(): break feedback.setProgress(int(current * total)) if not ft.hasGeometry(): continue # if ft[line.name()] != lineN: if xyzf != []: lines.append([lineN, nL]) the_csv = os.path.join(self.tmpDir, '%s.csv' % str(lineN)) with codecs.open(the_csv, 'w', 'utf-8') as fo: fo.write('X,Y,FID,Data\n') for ar in xyzf: fo.write(','.join(map(str, ar))) fo.write('\n') le = sqrt((xyzf[0][0] - xyzf[-1][0])**2 + (xyzf[0][1] - xyzf[-1][1])**2) if le > TL: TL = le xyzf = [] nL = 0 lineN = ft[line.name()] # rdata = float(ft[data.name()]) fiduu = int(ft[fidu.name()]) if abs(rdata - dumval) < 1e-6: # Dummy value: skip continue # stat.addVariant(ft[data.name()]) # how to handle QgsMultiPoint ??? if (the_layer.wkbType() == QgsWkbTypes.MultiPoint or the_layer.wkbType() == QgsWkbTypes.MultiPointM or the_layer.wkbType() == QgsWkbTypes.MultiPointZ or the_layer.wkbType() == QgsWkbTypes.MultiPointZM or the_layer.wkbType() == QgsWkbTypes.MultiPoint25D): # Suppose they all have the same attributes: # in this case it seems useless to get more than the first point, but... points = ft.geometry().constGet().clone() else: points = [ft.geometry().constGet().clone()] try: for point in points: xyzf.append([point.x(), point.y(), fiduu, rdata]) nL += 1 except: pass # last line if xyzf != []: lines.append([lineN, nL]) the_csv = os.path.join(self.tmpDir, '%s.csv' % str(lineN)) with codecs.open(the_csv, 'w', 'utf-8') as fo: fo.write('X,Y,FID,Data\n') for ar in xyzf: fo.write(','.join(map(str, ar))) fo.write('\n') le = sqrt((xyzf[0][0] - xyzf[-1][0])**2 + (xyzf[0][1] - xyzf[-1][1])**2) if le > TL: TL = le # stat.finalize() self.dmean = stat.mean() self.mult = TL / (stat.max() - stat.min()) # if bCHscal: # Scaling field: retrieve its stats scch_fld = self.parameterAsString(parameters, self.SCALCH, context) scch = scally.fields().at(scally.fields().lookupField(scch_fld)) scch_ix = scally.fields().lookupField(scch_fld) scch_f = scally.getFeatures( QgsFeatureRequest().setSubsetOfAttributes([scch_ix]), QgsProcessingFeatureSource.FlagSkipGeometryValidityChecks) stat = QgsStatisticalSummary() for current, ft in enumerate(scch_f): stat.addVariant(ft[scch.name()]) stat.finalize() self.dmean = stat.mean() self.mult = TL / (stat.max() - stat.min()) # if invP: iv = -1 else: iv = 1 # Profile total = 40.0 / (len(lines) + 1) # For each line: for current, z in enumerate(lines): line = z[0] if feedback.isCanceled(): break if not ft.hasGeometry(): continue feedback.setProgress(int(current * total) + 60.) # Read line back from csv the_csv = os.path.join(self.tmpDir, '%s.csv' % str(line)) if not os.path.exists(the_csv): raise ValueError( 'It seems parameters are swaped: LINE <-> DATA!') ar = pd.read_csv(the_csv) ar = ar.sort_values('FID') # Create the profile px, py = self._do_profile(ar, iv, scale, offset) #Construct vector layer f = QgsFeature() typeL = str(self.type) azimut = float(self.azimut) Len = float(self.length) CLen = float(self.clength) f.setAttributes( [str(line), typeL, int(len(px)), azimut, Len, CLen]) line_pts = [ QgsPoint(x, y, m=m) for x, y, m in zip(px, py, ar.Data) ] if join_to_line: # Join profile to its line e = len(ar) - 1 ar0 = [QgsPoint(ar.X[0], ar.Y[0], m=0.)] ar1 = [QgsPoint(ar.X[e], ar.Y[e], m=0.)] line_pts = ar0 + line_pts + ar1 # f.setGeometry(QgsGeometry(QgsLineString(line_pts))) sink.addFeature(f, QgsFeatureSink.FastInsert) # Delete temp csv file try: os.remove(the_csv) except: pass return {self.OUTPUT: dest_id}
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT)) fields = source.fields() fields.append(QgsField('vertex_pos', QVariant.Int)) fields.append(QgsField('vertex_index', QVariant.Int)) fields.append(QgsField('vertex_part', QVariant.Int)) if QgsWkbTypes.geometryType( source.wkbType()) == QgsWkbTypes.PolygonGeometry: fields.append(QgsField('vertex_part_ring', QVariant.Int)) fields.append(QgsField('vertex_part_index', QVariant.Int)) fields.append(QgsField('distance', QVariant.Double)) fields.append(QgsField('angle', QVariant.Double)) wkb_type = QgsWkbTypes.Point if QgsWkbTypes.hasM(source.wkbType()): wkb_type = QgsWkbTypes.addM(wkb_type) if QgsWkbTypes.hasZ(source.wkbType()): wkb_type = QgsWkbTypes.addZ(wkb_type) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, wkb_type, source.sourceCrs(), QgsFeatureSink.RegeneratePrimaryKey) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) vertex_indices_string = self.parameterAsString(parameters, self.VERTICES, context) indices = [] for vertex in vertex_indices_string.split(','): try: indices.append(int(vertex)) except: raise QgsProcessingException( self.tr('\'{}\' is not a valid vertex index').format( vertex)) features = source.getFeatures( QgsFeatureRequest(), QgsProcessingFeatureSource.FlagSkipGeometryValidityChecks) total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, f in enumerate(features): if feedback.isCanceled(): break input_geometry = f.geometry() if not input_geometry: sink.addFeature(f, QgsFeatureSink.FastInsert) else: total_vertices = input_geometry.constGet().nCoordinates() for vertex in indices: if vertex < 0: vertex_index = total_vertices + vertex else: vertex_index = vertex if vertex_index < 0 or vertex_index >= total_vertices: continue (success, vertex_id ) = input_geometry.vertexIdFromVertexNr(vertex_index) distance = input_geometry.distanceToVertex(vertex_index) angle = math.degrees( input_geometry.angleAtVertex(vertex_index)) output_feature = QgsFeature() attrs = f.attributes() attrs.append(vertex) attrs.append(vertex_index) attrs.append(vertex_id.part) if QgsWkbTypes.geometryType( source.wkbType()) == QgsWkbTypes.PolygonGeometry: attrs.append(vertex_id.ring) attrs.append(vertex_id.vertex) attrs.append(distance) attrs.append(angle) output_feature.setAttributes(attrs) point = input_geometry.vertexAt(vertex_index) output_feature.setGeometry(QgsGeometry(point)) sink.addFeature(output_feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT)) method = self.parameterAsEnum(parameters, self.METHOD, context) wkb_type = source.wkbType() fields = source.fields() new_fields = QgsFields() if QgsWkbTypes.geometryType(wkb_type) == QgsWkbTypes.PolygonGeometry: new_fields.append(QgsField('area', QVariant.Double)) new_fields.append(QgsField('perimeter', QVariant.Double)) elif QgsWkbTypes.geometryType(wkb_type) == QgsWkbTypes.LineGeometry: new_fields.append(QgsField('length', QVariant.Double)) if not QgsWkbTypes.isMultiType(source.wkbType()): new_fields.append(QgsField('straightdis', QVariant.Double)) new_fields.append(QgsField('sinuosity', QVariant.Double)) else: new_fields.append(QgsField('xcoord', QVariant.Double)) new_fields.append(QgsField('ycoord', QVariant.Double)) if QgsWkbTypes.hasZ(source.wkbType()): self.export_z = True new_fields.append(QgsField('zcoord', QVariant.Double)) if QgsWkbTypes.hasM(source.wkbType()): self.export_m = True new_fields.append(QgsField('mvalue', QVariant.Double)) fields = QgsProcessingUtils.combineFields(fields, new_fields) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, wkb_type, source.sourceCrs()) if sink is None: raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT)) coordTransform = None # Calculate with: # 0 - layer CRS # 1 - project CRS # 2 - ellipsoidal self.distance_area = QgsDistanceArea() if method == 2: self.distance_area.setSourceCrs(source.sourceCrs(), context.transformContext()) self.distance_area.setEllipsoid(context.project().ellipsoid()) elif method == 1: coordTransform = QgsCoordinateTransform(source.sourceCrs(), context.project().crs(), context.project()) features = source.getFeatures() total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, f in enumerate(features): if feedback.isCanceled(): break outFeat = f attrs = f.attributes() inGeom = f.geometry() if inGeom: if coordTransform is not None: inGeom.transform(coordTransform) if inGeom.type() == QgsWkbTypes.PointGeometry: attrs.extend(self.point_attributes(inGeom)) elif inGeom.type() == QgsWkbTypes.PolygonGeometry: attrs.extend(self.polygon_attributes(inGeom)) else: attrs.extend(self.line_attributes(inGeom)) # ensure consistent count of attributes - otherwise null # geometry features will have incorrect attribute length # and provider may reject them if len(attrs) < len(fields): attrs += [NULL] * (len(fields) - len(attrs)) outFeat.setAttributes(attrs) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT)) method = self.parameterAsEnum(parameters, self.METHOD, context) wkb_type = source.wkbType() if QgsWkbTypes.geometryType(wkb_type) != QgsWkbTypes.PolygonGeometry: # TODO IMPROVE FEEDBACK feedback.reportError('The layer geometry type is different from a polygon') return {} fields = source.fields() new_fields = QgsFields() new_fields.append(QgsField('perimeter', QVariant.Double)) new_fields.append(QgsField('area', QVariant.Double)) fields = QgsProcessingUtils.combineFields(fields, new_fields) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, wkb_type, source.sourceCrs()) if sink is None: raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT)) coordTransform = None # Calculate with: # 0 - layer CRS # 1 - project CRS # 2 - ellipsoidal self.distance_area = QgsDistanceArea() if method == 2: self.distance_area.setSourceCrs(source.sourceCrs(), context.transformContext()) self.distance_area.setEllipsoid(context.ellipsoid()) elif method == 1: if not context.project(): raise QgsProcessingException(self.tr('No project is available in this context')) coordTransform = QgsCoordinateTransform(source.sourceCrs(), context.project().crs(), context.project()) features = source.getFeatures() total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, f in enumerate(features): if feedback.isCanceled(): break outFeat = f attrs = f.attributes() inGeom = f.geometry() if inGeom: if coordTransform is not None: inGeom.transform(coordTransform) attrs.extend(self.polygon_attributes(inGeom)) # ensure consistent count of attributes - otherwise null # geometry features will have incorrect attribute length # and provider may reject them if len(attrs) < len(fields): attrs += [NULL] * (len(fields) - len(attrs)) outFeat.setAttributes(attrs) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
def make_features_compatible(new_features, input_layer): """Try to make the new features compatible with old features by: - converting single to multi part - dropping additional attributes - adding back M/Z values - drop Z/M - convert multi part to single part :param new_features: new features :type new_features: list of QgsFeatures :param input_layer: input layer :type input_layer: QgsVectorLayer :return: modified features :rtype: list of QgsFeatures """ input_wkb_type = input_layer.wkbType() result_features = [] for new_f in new_features: # Fix attributes if new_f.fields().count() > 0: attributes = [] for field in input_layer.fields(): if new_f.fields().indexFromName(field.name()) >= 0: attributes.append(new_f[field.name()]) else: attributes.append(None) f = QgsFeature(input_layer.fields()) f.setAttributes(attributes) f.setGeometry(new_f.geometry()) new_f = f else: lendiff = len(new_f.attributes()) - len(input_layer.fields()) if lendiff > 0: f = QgsFeature(input_layer.fields()) f.setGeometry(new_f.geometry()) f.setAttributes(new_f.attributes()[:len(input_layer.fields())]) new_f = f elif lendiff < 0: f = QgsFeature(input_layer.fields()) f.setGeometry(new_f.geometry()) attributes = new_f.attributes() + [ None for i in range(-lendiff) ] f.setAttributes(attributes) new_f = f # Check if we need geometry manipulation new_f_geom_type = QgsWkbTypes.geometryType(new_f.geometry().wkbType()) new_f_has_geom = new_f_geom_type not in (QgsWkbTypes.UnknownGeometry, QgsWkbTypes.NullGeometry) input_layer_has_geom = input_wkb_type not in (QgsWkbTypes.NoGeometry, QgsWkbTypes.Unknown) # Drop geometry if layer is geometry-less if not input_layer_has_geom and new_f_has_geom: f = QgsFeature(input_layer.fields()) f.setAttributes(new_f.attributes()) new_f = f result_features.append(new_f) continue # skip the rest if input_layer_has_geom and new_f_has_geom and \ new_f.geometry().wkbType() != input_wkb_type: # Fix geometry # Single -> Multi if (QgsWkbTypes.isMultiType(input_wkb_type) and not new_f.geometry().isMultipart()): new_geom = new_f.geometry() new_geom.convertToMultiType() new_f.setGeometry(new_geom) # Drop Z/M if (new_f.geometry().constGet().is3D() and not QgsWkbTypes.hasZ(input_wkb_type)): new_geom = new_f.geometry() new_geom.get().dropZValue() new_f.setGeometry(new_geom) if (new_f.geometry().constGet().isMeasure() and not QgsWkbTypes.hasM(input_wkb_type)): new_geom = new_f.geometry() new_geom.get().dropMValue() new_f.setGeometry(new_geom) # Add Z/M back (set it to 0) if (not new_f.geometry().constGet().is3D() and QgsWkbTypes.hasZ(input_wkb_type)): new_geom = new_f.geometry() new_geom.get().addZValue(0.0) new_f.setGeometry(new_geom) if (not new_f.geometry().constGet().isMeasure() and QgsWkbTypes.hasM(input_wkb_type)): new_geom = new_f.geometry() new_geom.get().addMValue(0.0) new_f.setGeometry(new_geom) # Multi -> Single if (not QgsWkbTypes.isMultiType(input_wkb_type) and new_f.geometry().isMultipart()): g = new_f.geometry() g2 = g.constGet() for i in range(g2.partCount()): # Clone or crash! g4 = QgsGeometry(g2.geometryN(i).clone()) f = QgsVectorLayerUtils.createFeature( input_layer, g4, { i: new_f.attribute(i) for i in range(new_f.fields().count()) }) result_features.append(f) else: result_features.append(new_f) else: result_features.append(new_f) return result_features
def processAlgorithm(self, parameters, context, feedback): sourceA = self.parameterAsSource(parameters, self.INPUT, context) sourceB = self.parameterAsSource(parameters, self.OVERLAY, context) geomType = QgsWkbTypes.multiType(sourceA.wkbType()) fieldsA = self.parameterAsFields(parameters, self.INPUT_FIELDS, context) fieldsB = self.parameterAsFields(parameters, self.OVERLAY_FIELDS, context) fieldListA = QgsFields() field_indices_a = [] if len(fieldsA) > 0: for f in fieldsA: idxA = sourceA.fields().lookupField(f) if idxA >= 0: field_indices_a.append(idxA) fieldListA.append(sourceA.fields()[idxA]) else: fieldListA = sourceA.fields() field_indices_a = [i for i in range(0, fieldListA.count())] fieldListB = QgsFields() field_indices_b = [] if len(fieldsB) > 0: for f in fieldsB: idxB = sourceB.fields().lookupField(f) if idxB >= 0: field_indices_b.append(idxB) fieldListB.append(sourceB.fields()[idxB]) else: fieldListB = sourceB.fields() field_indices_b = [i for i in range(0, fieldListB.count())] output_fields = QgsProcessingUtils.combineFields(fieldListA, fieldListB) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, output_fields, geomType, sourceA.sourceCrs()) outFeat = QgsFeature() indexB = QgsSpatialIndex(sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(sourceA.sourceCrs())), feedback) total = 100.0 / sourceA.featureCount() if sourceA.featureCount() else 1 count = 0 for featA in sourceA.getFeatures(QgsFeatureRequest().setSubsetOfAttributes(field_indices_a)): if feedback.isCanceled(): break if not featA.hasGeometry(): continue geom = featA.geometry() atMapA = featA.attributes() intersects = indexB.intersects(geom.boundingBox()) request = QgsFeatureRequest().setFilterFids(intersects) request.setDestinationCrs(sourceA.sourceCrs()) request.setSubsetOfAttributes(field_indices_b) engine = None if len(intersects) > 0: # use prepared geometries for faster intersection tests engine = QgsGeometry.createGeometryEngine(geom.geometry()) engine.prepareGeometry() for featB in sourceB.getFeatures(request): if feedback.isCanceled(): break tmpGeom = featB.geometry() if engine.intersects(tmpGeom.geometry()): out_attributes = [featA.attributes()[i] for i in field_indices_a] out_attributes.extend([featB.attributes()[i] for i in field_indices_b]) int_geom = QgsGeometry(geom.intersection(tmpGeom)) if int_geom.wkbType() == QgsWkbTypes.Unknown or QgsWkbTypes.flatType(int_geom.geometry().wkbType()) == QgsWkbTypes.GeometryCollection: int_com = geom.combine(tmpGeom) int_geom = QgsGeometry() if int_com: int_sym = geom.symDifference(tmpGeom) int_geom = QgsGeometry(int_com.difference(int_sym)) if int_geom.isEmpty() or not int_geom.isGeosValid(): raise QgsProcessingException( self.tr('GEOS geoprocessing error: One or ' 'more input features have invalid ' 'geometry.')) try: if QgsWkbTypes.geometryType(int_geom.wkbType()) == QgsWkbTypes.geometryType(geomType): int_geom.convertToMultiType() outFeat.setGeometry(int_geom) outFeat.setAttributes(out_attributes) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: raise QgsProcessingException( self.tr('Feature geometry error: One or more ' 'output features ignored due to invalid ' 'geometry.')) count += 1 feedback.setProgress(int(count * total)) return {self.OUTPUT: dest_id}
def processAlgorithm(self, progress): layerA = dataobjects.getObjectFromUri( self.getParameterValue(self.INPUT_A)) splitLayer = dataobjects.getObjectFromUri( self.getParameterValue(self.INPUT_B)) sameLayer = self.getParameterValue( self.INPUT_A) == self.getParameterValue(self.INPUT_B) fieldList = layerA.fields() writer = self.getOutputFromName(self.OUTPUT).getVectorWriter( fieldList, layerA.wkbType(), layerA.crs()) spatialIndex = QgsSpatialIndex() splitGeoms = {} request = QgsFeatureRequest() request.setSubsetOfAttributes([]) for aSplitFeature in vector.features(splitLayer, request): splitGeoms[aSplitFeature.id()] = aSplitFeature.geometry() spatialIndex.insertFeature(aSplitFeature) # honor the case that user has selection on split layer and has setting "use selection" outFeat = QgsFeature() features = vector.features(layerA) if len(features) == 0: total = 100 else: total = 100.0 / float(len(features)) multiGeoms = 0 # how many multi geometries were encountered for current, inFeatA in enumerate(features): inGeom = inFeatA.geometry() if inGeom.isMultipart(): multiGeoms += 1 # MultiGeometries are not allowed because the result of a splitted part cannot be clearly defined: # 1) add both new parts as new features # 2) store one part as a new feature and the other one as part of the multi geometry # 2a) which part should be which, seems arbitrary else: attrsA = inFeatA.attributes() outFeat.setAttributes(attrsA) inGeoms = [inGeom] lines = spatialIndex.intersects(inGeom.boundingBox()) if len(lines) > 0: # has intersection of bounding boxes splittingLines = [] engine = QgsGeometry.createGeometryEngine( inGeom.geometry()) engine.prepareGeometry() for i in lines: try: splitGeom = splitGeoms[i] except: continue # check if trying to self-intersect if sameLayer: if inFeatA.id() == i: continue if engine.intersects(splitGeom.geometry()): splittingLines.append(splitGeom) if len(splittingLines) > 0: for splitGeom in splittingLines: splitterPList = None outGeoms = [] split_geom_engine = QgsGeometry.createGeometryEngine( splitGeom.geometry()) split_geom_engine.prepareGeometry() while len(inGeoms) > 0: inGeom = inGeoms.pop() if split_geom_engine.intersects( inGeom.geometry()): inPoints = vector.extractPoints(inGeom) if splitterPList == None: splitterPList = vector.extractPoints( splitGeom) try: result, newGeometries, topoTestPoints = inGeom.splitGeometry( splitterPList, False) except: ProcessingLog.addToLog( ProcessingLog.LOG_WARNING, self. tr('Geometry exception while splitting' )) result = 1 # splitGeometry: If there are several intersections # between geometry and splitLine, only the first one is considered. if result == 0: # split occurred if inPoints == vector.extractPoints( inGeom): # bug in splitGeometry: sometimes it returns 0 but # the geometry is unchanged QgsMessageLog.logMessage( "appending") outGeoms.append(inGeom) else: inGeoms.append(inGeom) for aNewGeom in newGeometries: inGeoms.append(aNewGeom) else: QgsMessageLog.logMessage( "appending else") outGeoms.append(inGeom) else: outGeoms.append(inGeom) inGeoms = outGeoms for aGeom in inGeoms: passed = True if QgsWkbTypes.geometryType( aGeom.wkbType() ) == QgsWkbTypes.LineGeometry \ and not QgsWkbTypes.isMultiType(aGeom.wkbType()): passed = len(aGeom.asPolyline()) > 2 if not passed: passed = (len(aGeom.asPolyline()) == 2 and aGeom.asPolyline()[0] != aGeom.asPolyline()[1]) # sometimes splitting results in lines of zero length if passed: outFeat.setGeometry(aGeom) writer.addFeature(outFeat) progress.setPercentage(int(current * total)) if multiGeoms > 0: ProcessingLog.addToLog( ProcessingLog.LOG_INFO, self. tr('Feature geometry error: %s input features ignored due to multi-geometry.' ) % str(multiGeoms)) del writer
def processAlgorithm(self, parameters, context, feedback): sourceA = self.parameterAsSource(parameters, self.INPUT, context) sourceB = self.parameterAsSource(parameters, self.OVERLAY, context) geomType = QgsWkbTypes.multiType(sourceA.wkbType()) fields = QgsProcessingUtils.combineFields(sourceA.fields(), sourceB.fields()) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, geomType, sourceA.sourceCrs()) featA = QgsFeature() featB = QgsFeature() outFeat = QgsFeature() indexA = QgsSpatialIndex(sourceA, feedback) indexB = QgsSpatialIndex( sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes( []).setDestinationCrs(sourceA.sourceCrs(), context.transformContext())), feedback) total = 100.0 / (sourceA.featureCount() * sourceB.featureCount()) if sourceA.featureCount( ) and sourceB.featureCount() else 1 count = 0 for featA in sourceA.getFeatures(): if feedback.isCanceled(): break lstIntersectingB = [] geom = featA.geometry() atMapA = featA.attributes() intersects = indexB.intersects(geom.boundingBox()) if len(intersects) < 1: try: geom.convertToMultiType() outFeat.setGeometry(geom) outFeat.setAttributes(atMapA) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: # This really shouldn't happen, as we haven't # edited the input geom at all feedback.pushInfo( self. tr('Feature geometry error: One or more output features ignored due to invalid geometry.' )) else: request = QgsFeatureRequest().setFilterFids( intersects).setSubsetOfAttributes([]) request.setDestinationCrs(sourceA.sourceCrs(), context.transformContext()) engine = QgsGeometry.createGeometryEngine(geom.constGet()) engine.prepareGeometry() for featB in sourceB.getFeatures(request): atMapB = featB.attributes() tmpGeom = featB.geometry() if engine.intersects(tmpGeom.constGet()): int_geom = geom.intersection(tmpGeom) lstIntersectingB.append(tmpGeom) if not int_geom: # There was a problem creating the intersection feedback.pushInfo( self. tr('Feature geometry error: One or more output features ignored due to invalid geometry.' )) int_geom = QgsGeometry() else: int_geom = QgsGeometry(int_geom) if int_geom.wkbType( ) == QgsWkbTypes.Unknown or QgsWkbTypes.flatType( int_geom.wkbType( )) == QgsWkbTypes.GeometryCollection: # Intersection produced different geomety types temp_list = int_geom.asGeometryCollection() for i in temp_list: if i.type() == geom.type(): int_geom = QgsGeometry(i) try: int_geom.convertToMultiType() outFeat.setGeometry(int_geom) outFeat.setAttributes(atMapA + atMapB) sink.addFeature( outFeat, QgsFeatureSink.FastInsert) except: feedback.pushInfo( self. tr('Feature geometry error: One or more output features ignored due to invalid geometry.' )) else: # Geometry list: prevents writing error # in geometries of different types # produced by the intersection # fix #3549 if QgsWkbTypes.geometryType(int_geom.wkbType( )) == QgsWkbTypes.geometryType(geomType): try: int_geom.convertToMultiType() outFeat.setGeometry(int_geom) outFeat.setAttributes(atMapA + atMapB) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: feedback.pushInfo( self. tr('Feature geometry error: One or more output features ignored due to invalid geometry.' )) # the remaining bit of featA's geometry # if there is nothing left, this will just silently fail and we're good diff_geom = QgsGeometry(geom) if len(lstIntersectingB) != 0: intB = QgsGeometry.unaryUnion(lstIntersectingB) diff_geom = diff_geom.difference(intB) if diff_geom.wkbType( ) == QgsWkbTypes.Unknown or QgsWkbTypes.flatType( diff_geom.wkbType()) == QgsWkbTypes.GeometryCollection: temp_list = diff_geom.asGeometryCollection() for i in temp_list: if i.type() == geom.type(): diff_geom = QgsGeometry(i) try: diff_geom.convertToMultiType() outFeat.setGeometry(diff_geom) outFeat.setAttributes(atMapA) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: feedback.pushInfo( self. tr('Feature geometry error: One or more output features ignored due to invalid geometry.' )) count += 1 feedback.setProgress(int(count * total)) length = len(sourceA.fields()) atMapA = [None] * length for featA in sourceB.getFeatures(QgsFeatureRequest().setDestinationCrs( sourceA.sourceCrs(), context.transformContext())): if feedback.isCanceled(): break add = False geom = featA.geometry() diff_geom = QgsGeometry(geom) atMap = [None] * length atMap.extend(featA.attributes()) intersects = indexA.intersects(geom.boundingBox()) if len(intersects) < 1: try: geom.convertToMultiType() outFeat.setGeometry(geom) outFeat.setAttributes(atMap) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: feedback.pushInfo( self. tr('Feature geometry error: One or more output features ignored due to invalid geometry.' )) else: request = QgsFeatureRequest().setFilterFids( intersects).setSubsetOfAttributes([]) request.setDestinationCrs(sourceA.sourceCrs(), context.transformContext()) # use prepared geometries for faster intersection tests engine = QgsGeometry.createGeometryEngine(diff_geom.constGet()) engine.prepareGeometry() for featB in sourceA.getFeatures(request): atMapB = featB.attributes() tmpGeom = featB.geometry() if engine.intersects(tmpGeom.constGet()): add = True diff_geom = QgsGeometry(diff_geom.difference(tmpGeom)) else: try: # Ihis only happens if the bounding box # intersects, but the geometry doesn't diff_geom.convertToMultiType() outFeat.setGeometry(diff_geom) outFeat.setAttributes(atMap) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: feedback.pushInfo( self. tr('Feature geometry error: One or more output features ignored due to invalid geometry.' )) if add: try: diff_geom.convertToMultiType() outFeat.setGeometry(diff_geom) outFeat.setAttributes(atMap) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: feedback.pushInfo( self. tr('Feature geometry error: One or more output features ignored due to invalid geometry.' )) count += 1 feedback.setProgress(int(count * total)) return {self.OUTPUT: dest_id}
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT)) method = self.parameterAsEnum(parameters, self.METHOD, context) wkb_type = source.wkbType() fields = source.fields() new_fields = QgsFields() if QgsWkbTypes.geometryType(wkb_type) == QgsWkbTypes.PolygonGeometry: new_fields.append(QgsField('area', QVariant.Double)) new_fields.append(QgsField('perimeter', QVariant.Double)) elif QgsWkbTypes.geometryType(wkb_type) == QgsWkbTypes.LineGeometry: new_fields.append(QgsField('length', QVariant.Double)) if not QgsWkbTypes.isMultiType(source.wkbType()): new_fields.append(QgsField('straightdis', QVariant.Double)) new_fields.append(QgsField('sinuosity', QVariant.Double)) else: if QgsWkbTypes.isMultiType(source.wkbType()): new_fields.append(QgsField('numparts', QVariant.Int)) else: new_fields.append(QgsField('xcoord', QVariant.Double)) new_fields.append(QgsField('ycoord', QVariant.Double)) if QgsWkbTypes.hasZ(source.wkbType()): self.export_z = True new_fields.append(QgsField('zcoord', QVariant.Double)) if QgsWkbTypes.hasM(source.wkbType()): self.export_m = True new_fields.append(QgsField('mvalue', QVariant.Double)) fields = QgsProcessingUtils.combineFields(fields, new_fields) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, wkb_type, source.sourceCrs()) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) coordTransform = None # Calculate with: # 0 - layer CRS # 1 - project CRS # 2 - ellipsoidal self.distance_area = QgsDistanceArea() if method == 2: self.distance_area.setSourceCrs(source.sourceCrs(), context.transformContext()) self.distance_area.setEllipsoid(context.project().ellipsoid()) elif method == 1: coordTransform = QgsCoordinateTransform(source.sourceCrs(), context.project().crs(), context.project()) features = source.getFeatures() total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, f in enumerate(features): if feedback.isCanceled(): break outFeat = f attrs = f.attributes() inGeom = f.geometry() if inGeom: if coordTransform is not None: inGeom.transform(coordTransform) if inGeom.type() == QgsWkbTypes.PointGeometry: attrs.extend(self.point_attributes(inGeom)) elif inGeom.type() == QgsWkbTypes.PolygonGeometry: attrs.extend(self.polygon_attributes(inGeom)) else: attrs.extend(self.line_attributes(inGeom)) # ensure consistent count of attributes - otherwise null # geometry features will have incorrect attribute length # and provider may reject them if len(attrs) < len(fields): attrs += [NULL] * (len(fields) - len(attrs)) outFeat.setAttributes(attrs) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
def processAlgorithm(self, parameters, context, feedback): unicity = self.parameterAsBoolean(parameters, self.UNICITY, context) source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT)) wkb_type = source.wkbType() if (QgsWkbTypes.geometryType(wkb_type) != QgsWkbTypes.PolygonGeometry and QgsWkbTypes.geometryType(wkb_type) != QgsWkbTypes.LineGeometry): feedback.reportError( 'The layer geometry type is different from a line or a polygon' ) return {} (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, source.fields(), QgsWkbTypes.LineString, source.sourceCrs()) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) all_segments = [] features = source.getFeatures() total = 100.0 / source.featureCount() if source.featureCount() else 0 if QgsWkbTypes.geometryType(wkb_type) == QgsWkbTypes.PolygonGeometry: if unicity: for current, f in enumerate(features): if feedback.isCanceled(): break if not f.hasGeometry(): continue else: for p in self.polygon_to_unique_segments( f.geometry(), all_segments): feat = QgsFeature() feat.setAttributes(f.attributes()) feat.setGeometry(p) sink.addFeature(feat, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) else: for current, f in enumerate(features): if feedback.isCanceled(): break if not f.hasGeometry(): continue else: for p in self.polygon_to_segments(f.geometry()): feat = QgsFeature() feat.setAttributes(f.attributes()) feat.setGeometry(p) sink.addFeature(feat, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) else: # LineGeometry if unicity: for current, f in enumerate(features): if feedback.isCanceled(): break if not f.hasGeometry(): continue else: for p in self.line_to_unique_segments( f.geometry(), all_segments): feat = QgsFeature() feat.setAttributes(f.attributes()) feat.setGeometry(p) sink.addFeature(feat, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) else: for current, f in enumerate(features): if feedback.isCanceled(): break if not f.hasGeometry(): continue else: for p in self.line_to_segments(f.geometry()): feat = QgsFeature() feat.setAttributes(f.attributes()) feat.setGeometry(p) sink.addFeature(feat, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
def processAlgorithm(self, progress): layerA = dataobjects.getObjectFromUri(self.getParameterValue(self.INPUT_A)) splitLayer = dataobjects.getObjectFromUri(self.getParameterValue(self.INPUT_B)) sameLayer = self.getParameterValue(self.INPUT_A) == self.getParameterValue(self.INPUT_B) fieldList = layerA.fields() writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(fieldList, QgsWkbTypes.multiType(layerA.wkbType()), layerA.crs()) spatialIndex = QgsSpatialIndex() splitGeoms = {} request = QgsFeatureRequest() request.setSubsetOfAttributes([]) for aSplitFeature in vector.features(splitLayer, request): splitGeoms[aSplitFeature.id()] = aSplitFeature.geometry() spatialIndex.insertFeature(aSplitFeature) # honor the case that user has selection on split layer and has setting "use selection" outFeat = QgsFeature() features = vector.features(layerA) if len(features) == 0: total = 100 else: total = 100.0 / float(len(features)) for current, inFeatA in enumerate(features): inGeom = inFeatA.geometry() attrsA = inFeatA.attributes() outFeat.setAttributes(attrsA) if inGeom.isMultipart(): inGeoms = [] for g in inGeom.asGeometryCollection(): inGeoms.append(g) else: inGeoms = [inGeom] lines = spatialIndex.intersects(inGeom.boundingBox()) if len(lines) > 0: # has intersection of bounding boxes splittingLines = [] engine = QgsGeometry.createGeometryEngine(inGeom.geometry()) engine.prepareGeometry() for i in lines: try: splitGeom = splitGeoms[i] except: continue # check if trying to self-intersect if sameLayer: if inFeatA.id() == i: continue if engine.intersects(splitGeom.geometry()): splittingLines.append(splitGeom) if len(splittingLines) > 0: for splitGeom in splittingLines: splitterPList = None outGeoms = [] split_geom_engine = QgsGeometry.createGeometryEngine(splitGeom.geometry()) split_geom_engine.prepareGeometry() while len(inGeoms) > 0: inGeom = inGeoms.pop() if inGeom.isEmpty(): # this has been encountered and created a run-time error continue if split_geom_engine.intersects(inGeom.geometry()): inPoints = vector.extractPoints(inGeom) if splitterPList == None: splitterPList = vector.extractPoints(splitGeom) try: result, newGeometries, topoTestPoints = inGeom.splitGeometry(splitterPList, False) except: ProcessingLog.addToLog(ProcessingLog.LOG_WARNING, self.tr('Geometry exception while splitting')) result = 1 # splitGeometry: If there are several intersections # between geometry and splitLine, only the first one is considered. if result == 0: # split occurred if inPoints == vector.extractPoints(inGeom): # bug in splitGeometry: sometimes it returns 0 but # the geometry is unchanged outGeoms.append(inGeom) else: inGeoms.append(inGeom) for aNewGeom in newGeometries: inGeoms.append(aNewGeom) else: outGeoms.append(inGeom) else: outGeoms.append(inGeom) inGeoms = outGeoms parts = [] for aGeom in inGeoms: passed = True if QgsWkbTypes.geometryType( aGeom.wkbType() ) == QgsWkbTypes.LineGeometry: numPoints = aGeom.geometry().numPoints() if numPoints <= 2: if numPoints == 2: passed = not aGeom.geometry().isClosed() # tests if vertex 0 = vertex 1 else: passed = False # sometimes splitting results in lines of zero length if passed: parts.append(aGeom) if len(parts) > 0: outFeat.setGeometry(QgsGeometry.collectGeometry(parts)) writer.addFeature(outFeat) progress.setPercentage(int(current * total)) del writer
def processAlgorithm(self, parameters, context, feedback): # get input variables source = self.parameterAsSource(parameters,self.INPUT,context) export_format = self.parameterAsEnum(parameters,self.EXPORT_FORMAT,context) output = self.parameterAsFileOutput(parameters,self.OUTPUT,context) # set new default values in config feedback.pushConsoleInfo(self.tr(f'Storing new default settings in config...')) self.config.set(self.module,'export_format',export_format) # coordinate transformation trans = QgsCoordinateTransform(source.sourceCrs(), QgsCoordinateReferenceSystem('EPSG:4326'), context.transformContext()) # geometry type geom_type = QgsWkbTypes.geometryType(source.wkbType()) if geom_type == QgsWkbTypes.LineGeometry: # fields to be created (empty) feedback.pushConsoleInfo(self.tr(f'Creating attribute fields...')) fields = QgsFields() # fields from feature source source_fields = source.fields() # if source does not have fid field, create it in fields if 'fid' not in source_fields.names(): fields.append(QgsField('fid',QVariant.Int,'',4,0)) # add all fields from source to fields variable for field in source_fields: fields.append(field) # get features from source features = source.getFeatures() # get vertices from features feedback.pushConsoleInfo(self.tr(f'Converting lines to vertices...')) points = self.lines_to_vertices(features,fields) elif geom_type == QgsWkbTypes.PointGeometry: # get points directly points = source.getFeatures() else: feedback.reportError(self.tr('Unknown Geometry WKB Type'),fatalError=True) return {} # empty table for point features and their attributes table = [] # create dict holding all features with fieldnames and geometry feedback.pushConsoleInfo(self.tr(f'Extracting feature attributes...')) for i, feature in enumerate(points): feature_fields = feature.fields().names() feature_attributes = feature.attributes() # add minimum of required fields if they don't exist if not 'fid' in feature_fields: feature_fields.append('fid') feature_attributes.append(i+1) if not 'name' in feature_fields: feature_fields.append('name') feature_attributes.append('') # zip lists to dict feature_dict = dict(zip(feature_fields,feature_attributes)) # get geometry of feature geom = feature.geometry().asPoint() # transform geometry to EPSG:4326 CRS feedback.pushConsoleInfo(self.tr(f'Adding coordinates...')) geom4326 = trans.transform(geom) feature_dict['lat_DD'] = geom4326.y() feature_dict['lon_DD'] = geom4326.x() # convert DD to DDM lat_ddm, lon_ddm = utils.dd2ddm(geom4326.y(), geom4326.x()) feature_dict['lat_DDM'] = lat_ddm feature_dict['lon_DDM'] = lon_ddm # clean up NULL values: for key, value in feature_dict.items(): if not type(value) in [float,int,str,bool]: if value.isNull(): feature_dict[key] = None table.append(feature_dict) # export functions feedback.pushConsoleInfo(self.tr(f'Exporting table...')) if export_format == 0: error, result = self.export_csv(table,output) elif export_format == 1: error, result = self.export_SAM_route(table,output) if error: feedback.reportError(self.tr(result),fatalError=True) return {} # 100% done feedback.setProgress(100) feedback.pushInfo(self.tr(f'{utils.return_success()}! Export file created!\n')) result = {self.OUTPUT : output} return result
def processAlgorithm(self, parameters, context, feedback): sourceA = self.parameterAsSource(parameters, self.INPUT, context) sourceB = self.parameterAsSource(parameters, self.OVERLAY, context) geomType = QgsWkbTypes.multiType(sourceA.wkbType()) fields = QgsProcessingUtils.combineFields(sourceA.fields(), sourceB.fields()) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, geomType, sourceA.sourceCrs()) featA = QgsFeature() featB = QgsFeature() outFeat = QgsFeature() indexA = QgsSpatialIndex(sourceA, feedback) indexB = QgsSpatialIndex(sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(sourceA.sourceCrs(), context.transformContext())), feedback) total = 100.0 / (sourceA.featureCount() * sourceB.featureCount()) if sourceA.featureCount() and sourceB.featureCount() else 1 count = 0 for featA in sourceA.getFeatures(): if feedback.isCanceled(): break lstIntersectingB = [] geom = featA.geometry() atMapA = featA.attributes() intersects = indexB.intersects(geom.boundingBox()) if len(intersects) < 1: try: geom.convertToMultiType() outFeat.setGeometry(geom) outFeat.setAttributes(atMapA) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: # This really shouldn't happen, as we haven't # edited the input geom at all feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.')) else: request = QgsFeatureRequest().setFilterFids(intersects).setSubsetOfAttributes([]) request.setDestinationCrs(sourceA.sourceCrs(), context.transformContext()) engine = QgsGeometry.createGeometryEngine(geom.constGet()) engine.prepareGeometry() for featB in sourceB.getFeatures(request): atMapB = featB.attributes() tmpGeom = featB.geometry() if engine.intersects(tmpGeom.constGet()): int_geom = geom.intersection(tmpGeom) lstIntersectingB.append(tmpGeom) if not int_geom: # There was a problem creating the intersection feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.')) int_geom = QgsGeometry() else: int_geom = QgsGeometry(int_geom) if int_geom.wkbType() == QgsWkbTypes.Unknown or QgsWkbTypes.flatType(int_geom.wkbType()) == QgsWkbTypes.GeometryCollection: # Intersection produced different geomety types temp_list = int_geom.asGeometryCollection() for i in temp_list: if i.type() == geom.type(): int_geom = QgsGeometry(i) try: int_geom.convertToMultiType() outFeat.setGeometry(int_geom) outFeat.setAttributes(atMapA + atMapB) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.')) else: # Geometry list: prevents writing error # in geometries of different types # produced by the intersection # fix #3549 if QgsWkbTypes.geometryType(int_geom.wkbType()) == QgsWkbTypes.geometryType(geomType): try: int_geom.convertToMultiType() outFeat.setGeometry(int_geom) outFeat.setAttributes(atMapA + atMapB) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.')) # the remaining bit of featA's geometry # if there is nothing left, this will just silently fail and we're good diff_geom = QgsGeometry(geom) if len(lstIntersectingB) != 0: intB = QgsGeometry.unaryUnion(lstIntersectingB) diff_geom = diff_geom.difference(intB) if diff_geom.wkbType() == QgsWkbTypes.Unknown or QgsWkbTypes.flatType(diff_geom.wkbType()) == QgsWkbTypes.GeometryCollection: temp_list = diff_geom.asGeometryCollection() for i in temp_list: if i.type() == geom.type(): diff_geom = QgsGeometry(i) try: diff_geom.convertToMultiType() outFeat.setGeometry(diff_geom) outFeat.setAttributes(atMapA) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.')) count += 1 feedback.setProgress(int(count * total)) length = len(sourceA.fields()) atMapA = [None] * length for featA in sourceB.getFeatures(QgsFeatureRequest().setDestinationCrs(sourceA.sourceCrs(), context.transformContext())): if feedback.isCanceled(): break add = False geom = featA.geometry() diff_geom = QgsGeometry(geom) atMap = [None] * length atMap.extend(featA.attributes()) intersects = indexA.intersects(geom.boundingBox()) if len(intersects) < 1: try: geom.convertToMultiType() outFeat.setGeometry(geom) outFeat.setAttributes(atMap) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.')) else: request = QgsFeatureRequest().setFilterFids(intersects).setSubsetOfAttributes([]) request.setDestinationCrs(sourceA.sourceCrs(), context.transformContext()) # use prepared geometries for faster intersection tests engine = QgsGeometry.createGeometryEngine(diff_geom.constGet()) engine.prepareGeometry() for featB in sourceA.getFeatures(request): atMapB = featB.attributes() tmpGeom = featB.geometry() if engine.intersects(tmpGeom.constGet()): add = True diff_geom = QgsGeometry(diff_geom.difference(tmpGeom)) else: try: # Ihis only happens if the bounding box # intersects, but the geometry doesn't diff_geom.convertToMultiType() outFeat.setGeometry(diff_geom) outFeat.setAttributes(atMap) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.')) if add: try: diff_geom.convertToMultiType() outFeat.setGeometry(diff_geom) outFeat.setAttributes(atMap) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.')) count += 1 feedback.setProgress(int(count * total)) return {self.OUTPUT: dest_id}
def processAlgorithm(self, parameters, context, feedback): ''' Here is where the processing itself takes place. ''' # if not is_dependencies_satisfied: return {} # Input layer the_layer = self.parameterAsSource(parameters, self.THE_LAYER, context) if the_layer is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.THE_LAYER)) # Output geometry if QgsWkbTypes.geometryType( the_layer.wkbType()) == QgsWkbTypes.PointGeometry: output_wkb = QgsWkbTypes.MultiPointZ elif QgsWkbTypes.geometryType( the_layer.wkbType()) == QgsWkbTypes.LineGeometry: output_wkb = QgsWkbTypes.MultiLineStringZ elif QgsWkbTypes.geometryType( the_layer.wkbType()) == QgsWkbTypes.PolygonGeometry: output_wkb = QgsWkbTypes.MultiPolygonZ else: raise QgsProcessingException( self.invalidSourceError(parameters, self.THE_LAYER)) # Y-value self._yval = self.parameterAsDouble(parameters, self.Y_VALUE, context) # Output file (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, the_layer.fields(), output_wkb, the_layer.sourceCrs()) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) total = 100.0 / the_layer.featureCount() if the_layer.featureCount( ) else 0 features = the_layer.getFeatures() for current, f in enumerate(features): if feedback.isCanceled(): break # Copy attributes fz = QgsFeature() fz.setAttributes(f.attributes()) # Get geometry geom = f.geometry() wkt = geom.asWkt() typ = wkt.split(" ")[0].strip() if typ in self._wktdef: nwkt = self._wktdef[typ](wkt) else: # Unrecognised geometry type raise ValueError('Unrecognised geometry type') fz.setGeometry(QgsGeometry.fromWkt(nwkt)) # Store output feature sink.addFeature(fz, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}