def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) fieldName = self.parameterAsString(parameters, self.FIELD, context) useField = bool(fieldName) field_index = None f = QgsField('value', QVariant.String, '', 255) if useField: field_index = source.fields().lookupField(fieldName) fType = source.fields()[field_index].type() if fType in [ QVariant.Int, QVariant.UInt, QVariant.LongLong, QVariant.ULongLong ]: f.setType(fType) f.setLength(20) elif fType == QVariant.Double: f.setType(QVariant.Double) f.setLength(20) f.setPrecision(6) else: f.setType(QVariant.String) f.setLength(255) fields = QgsFields() fields.append(QgsField('id', QVariant.Int, '', 20)) fields.append(f) fields.append(QgsField('area', QVariant.Double, '', 20, 6)) fields.append(QgsField('perim', QVariant.Double, '', 20, 6)) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, QgsWkbTypes.Polygon, source.sourceCrs()) outFeat = QgsFeature() outGeom = QgsGeometry() fid = 0 val = None if useField: unique = source.uniqueValues(field_index) current = 0 total = 100.0 / (source.featureCount() * len(unique)) if source.featureCount() else 1 for i in unique: if feedback.isCanceled(): break first = True hull = [] features = source.getFeatures( QgsFeatureRequest().setSubsetOfAttributes([field_index])) for f in features: if feedback.isCanceled(): break idVar = f.attributes()[field_index] if str(idVar).strip() == str(i).strip(): if first: val = idVar first = False inGeom = f.geometry() points = vector.extractPoints(inGeom) hull.extend(points) current += 1 feedback.setProgress(int(current * total)) if len(hull) >= 3: tmpGeom = QgsGeometry(outGeom.fromMultiPoint(hull)) try: outGeom = tmpGeom.convexHull() if outGeom: area = outGeom.geometry().area() perim = outGeom.geometry().perimeter() else: area = NULL perim = NULL outFeat.setGeometry(outGeom) outFeat.setAttributes([fid, val, area, perim]) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: raise QgsProcessingException( self.tr('Exception while computing convex hull')) fid += 1 else: hull = [] total = 100.0 / source.featureCount() if source.featureCount( ) else 1 features = source.getFeatures( QgsFeatureRequest().setSubsetOfAttributes([])) for current, f in enumerate(features): if feedback.isCanceled(): break inGeom = f.geometry() points = vector.extractPoints(inGeom) hull.extend(points) feedback.setProgress(int(current * total)) tmpGeom = QgsGeometry(outGeom.fromMultiPoint(hull)) try: outGeom = tmpGeom.convexHull() if outGeom: area = outGeom.geometry().area() perim = outGeom.geometry().perimeter() else: area = NULL perim = NULL outFeat.setGeometry(outGeom) outFeat.setAttributes([0, 'all', area, perim]) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: raise QgsProcessingException( self.tr('Exception while computing convex hull')) return {self.OUTPUT: dest_id}
def getConsoleCommands(self, parameters, context, feedback, executing=True): inLayer = self.parameterAsRasterLayer(parameters, self.INPUT, context) if inLayer is None: raise QgsProcessingException( self.invalidRasterError(parameters, self.INPUT)) maskLayer, maskLayerName = self.getOgrCompatibleSource( self.MASK, parameters, context, feedback, executing) sourceCrs = self.parameterAsCrs(parameters, self.SOURCE_CRS, context) targetCrs = self.parameterAsCrs(parameters, self.TARGET_CRS, context) if self.NODATA in parameters and parameters[self.NODATA] is not None: nodata = self.parameterAsDouble(parameters, self.NODATA, context) else: nodata = None options = self.parameterAsString(parameters, self.OPTIONS, context) out = self.parameterAsOutputLayer(parameters, self.OUTPUT, context) self.setOutputValue(self.OUTPUT, out) arguments = [] if sourceCrs.isValid(): arguments.append('-s_srs') arguments.append(GdalUtils.gdal_crs_string(sourceCrs)) if targetCrs.isValid(): arguments.append('-t_srs') arguments.append(GdalUtils.gdal_crs_string(targetCrs)) data_type = self.parameterAsEnum(parameters, self.DATA_TYPE, context) if data_type: arguments.append('-ot ' + self.TYPES[data_type]) arguments.append('-of') arguments.append( QgsRasterFileWriter.driverForExtension(os.path.splitext(out)[1])) if self.parameterAsBool(parameters, self.KEEP_RESOLUTION, context): arguments.append('-tr') arguments.append(str(inLayer.rasterUnitsPerPixelX())) arguments.append(str(-inLayer.rasterUnitsPerPixelY())) arguments.append('-tap') if self.parameterAsBool(parameters, self.SET_RESOLUTION, context): arguments.append('-tr') if self.X_RESOLUTION in parameters and parameters[ self.X_RESOLUTION] is not None: xres = self.parameterAsDouble(parameters, self.X_RESOLUTION, context) arguments.append('{}'.format(xres)) else: arguments.append(str(inLayer.rasterUnitsPerPixelX())) if self.Y_RESOLUTION in parameters and parameters[ self.Y_RESOLUTION] is not None: yres = self.parameterAsDouble(parameters, self.Y_RESOLUTION, context) arguments.append('{}'.format(yres)) else: arguments.append(str(-inLayer.rasterUnitsPerPixelY())) arguments.append('-tap') arguments.append('-cutline') arguments.append(maskLayer) if self.parameterAsBool(parameters, self.CROP_TO_CUTLINE, context): arguments.append('-crop_to_cutline') if self.parameterAsBool(parameters, self.ALPHA_BAND, context): arguments.append('-dstalpha') if nodata is not None: arguments.append('-dstnodata {}'.format(nodata)) if self.parameterAsBool(parameters, self.MULTITHREADING, context): arguments.append('-multi') if options: arguments.extend(GdalUtils.parseCreationOptions(options)) arguments.append(inLayer.source()) arguments.append(out) return [self.commandName(), GdalUtils.escapeAndJoin(arguments)]
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 doCheck(self, method, parameters, context, feedback, ignore_ring_self_intersection): flags = QgsGeometry.FlagAllowSelfTouchingHoles if ignore_ring_self_intersection else QgsGeometry.ValidityFlags() source = self.parameterAsSource(parameters, self.INPUT_LAYER, context) if source is None: raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT_LAYER)) (valid_output_sink, valid_output_dest_id) = self.parameterAsSink(parameters, self.VALID_OUTPUT, context, source.fields(), source.wkbType(), source.sourceCrs()) valid_count = 0 invalid_fields = source.fields() invalid_fields.append(QgsField('_errors', QVariant.String, 'string', 255)) (invalid_output_sink, invalid_output_dest_id) = self.parameterAsSink(parameters, self.INVALID_OUTPUT, context, invalid_fields, source.wkbType(), source.sourceCrs()) invalid_count = 0 error_fields = QgsFields() error_fields.append(QgsField('message', QVariant.String, 'string', 255)) (error_output_sink, error_output_dest_id) = self.parameterAsSink(parameters, self.ERROR_OUTPUT, context, error_fields, QgsWkbTypes.Point, source.sourceCrs()) error_count = 0 features = source.getFeatures(QgsFeatureRequest(), QgsProcessingFeatureSource.FlagSkipGeometryValidityChecks) total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, inFeat in enumerate(features): if feedback.isCanceled(): break geom = inFeat.geometry() attrs = inFeat.attributes() valid = True if not geom.isNull() and not geom.isEmpty(): errors = list(geom.validateGeometry(method, flags)) if errors: valid = False reasons = [] for error in errors: errFeat = QgsFeature() error_geom = QgsGeometry.fromPointXY(error.where()) errFeat.setGeometry(error_geom) errFeat.setAttributes([error.what()]) if error_output_sink: error_output_sink.addFeature(errFeat, QgsFeatureSink.FastInsert) error_count += 1 reasons.append(error.what()) reason = "\n".join(reasons) if len(reason) > 255: reason = reason[:252] + '…' attrs.append(reason) outFeat = QgsFeature() outFeat.setGeometry(geom) outFeat.setAttributes(attrs) if valid: if valid_output_sink: valid_output_sink.addFeature(outFeat, QgsFeatureSink.FastInsert) valid_count += 1 else: if invalid_output_sink: invalid_output_sink.addFeature(outFeat, QgsFeatureSink.FastInsert) invalid_count += 1 feedback.setProgress(int(current * total)) results = { self.VALID_COUNT: valid_count, self.INVALID_COUNT: invalid_count, self.ERROR_COUNT: error_count } if valid_output_sink: results[self.VALID_OUTPUT] = valid_output_dest_id if invalid_output_sink: results[self.INVALID_OUTPUT] = invalid_output_dest_id if error_output_sink: results[self.ERROR_OUTPUT] = error_output_dest_id return results
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT)) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, source.fields(), source.wkbType(), source.sourceCrs()) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) features = source.getFeatures( QgsFeatureRequest().setSubsetOfAttributes([])) total = 100.0 / source.featureCount() if source.featureCount() else 0 geoms = dict() null_geom_features = set() index = QgsSpatialIndex() for current, f in enumerate(features): if feedback.isCanceled(): break if not f.hasGeometry(): null_geom_features.add(f.id()) continue geoms[f.id()] = f.geometry() index.addFeature(f) feedback.setProgress(int(0.10 * current * total)) # takes about 10% of time # start by assuming everything is unique, and chop away at this list unique_features = dict(geoms) current = 0 removed = 0 for feature_id, geometry in geoms.items(): if feedback.isCanceled(): break if feature_id not in unique_features: # feature was already marked as a duplicate continue candidates = index.intersects(geometry.boundingBox()) candidates.remove(feature_id) for candidate_id in candidates: if candidate_id not in unique_features: # candidate already marked as a duplicate (not sure if this is possible, # since it would mean the current feature would also have to be a duplicate! # but let's be safe!) continue if geometry.isGeosEqual(geoms[candidate_id]): # candidate is a duplicate of feature del unique_features[candidate_id] removed += 1 current += 1 feedback.setProgress(int(0.80 * current * total) + 10) # takes about 80% of time # now, fetch all the feature attributes for the unique features only # be super-smart and don't re-fetch geometries distinct_geoms = set(unique_features.keys()) output_feature_ids = distinct_geoms.union(null_geom_features) total = 100.0 / len(output_feature_ids) if output_feature_ids else 1 request = QgsFeatureRequest().setFilterFids( list(output_feature_ids)).setFlags(QgsFeatureRequest.NoGeometry) for current, f in enumerate(source.getFeatures(request)): if feedback.isCanceled(): break # use already fetched geometry if f.id() not in null_geom_features: f.setGeometry(unique_features[f.id()]) sink.addFeature(f, QgsFeatureSink.FastInsert) feedback.setProgress(int(0.10 * current * total) + 90) # takes about 10% of time feedback.pushInfo( self.tr('{} duplicate features removed'.format(removed))) return { self.OUTPUT: dest_id, self.DUPLICATE_COUNT: removed, self.RETAINED_COUNT: len(output_feature_ids) }
def __init__(self, map_theme, layer, extent, tile_size, mupp, output, make_trans, map_settings): """ :param map_theme: :param extent: :param layer: :param tile_size: :param mupp: :param output: :param map_settings: Map canvas map settings used for some fallback values and CRS """ self.extent = extent self.mupp = mupp self.tile_size = tile_size driver = self.getDriverForFile(output) if not driver: raise QgsProcessingException( u'Could not load GDAL driver for file {}'.format(output)) crs = map_settings.destinationCrs() self.x_tile_count = math.ceil(extent.width() / mupp / tile_size) self.y_tile_count = math.ceil(extent.height() / mupp / tile_size) xsize = self.x_tile_count * tile_size ysize = self.y_tile_count * tile_size if make_trans: no_bands = 4 else: no_bands = 3 self.dataset = driver.Create(output, xsize, ysize, no_bands) self.dataset.SetProjection(str(crs.toWkt())) self.dataset.SetGeoTransform( [extent.xMinimum(), mupp, 0, extent.yMaximum(), 0, -mupp]) self.image = QImage(QSize(tile_size, tile_size), QImage.Format_ARGB32) self.settings = QgsMapSettings() self.settings.setOutputDpi(self.image.logicalDpiX()) self.settings.setOutputImageFormat(QImage.Format_ARGB32) self.settings.setDestinationCrs(crs) self.settings.setOutputSize(self.image.size()) self.settings.setFlag(QgsMapSettings.Antialiasing, True) self.settings.setFlag(QgsMapSettings.RenderMapTile, True) self.settings.setFlag(QgsMapSettings.UseAdvancedEffects, True) if make_trans: self.settings.setBackgroundColor(QColor(255, 255, 255, 0)) else: self.settings.setBackgroundColor(QColor(255, 255, 255)) if QgsProject.instance().mapThemeCollection().hasMapTheme(map_theme): self.settings.setLayers(QgsProject.instance().mapThemeCollection(). mapThemeVisibleLayers(map_theme)) self.settings.setLayerStyleOverrides(QgsProject.instance( ).mapThemeCollection().mapThemeStyleOverrides(map_theme)) elif layer: self.settings.setLayers([layer]) else: self.settings.setLayers(map_settings.layers())
def processAlgorithm(self, parameters, context, feedback): network = self.parameterAsSource(parameters, self.INPUT, context) if network is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT)) startPoint = self.parameterAsPoint(parameters, self.START_POINT, context, network.sourceCrs()) strategy = self.parameterAsEnum(parameters, self.STRATEGY, context) travelCost = self.parameterAsDouble(parameters, self.TRAVEL_COST, context) directionFieldName = self.parameterAsString(parameters, self.DIRECTION_FIELD, context) forwardValue = self.parameterAsString(parameters, self.VALUE_FORWARD, context) backwardValue = self.parameterAsString(parameters, self.VALUE_BACKWARD, context) bothValue = self.parameterAsString(parameters, self.VALUE_BOTH, context) defaultDirection = self.parameterAsEnum(parameters, self.DEFAULT_DIRECTION, context) speedFieldName = self.parameterAsString(parameters, self.SPEED_FIELD, context) defaultSpeed = self.parameterAsDouble(parameters, self.DEFAULT_SPEED, context) tolerance = self.parameterAsDouble(parameters, self.TOLERANCE, context) include_bounds = True # default to true to maintain 3.0 API if self.INCLUDE_BOUNDS in parameters: include_bounds = self.parameterAsBool(parameters, self.INCLUDE_BOUNDS, context) directionField = -1 if directionFieldName: directionField = network.fields().lookupField(directionFieldName) speedField = -1 if speedFieldName: speedField = network.fields().lookupField(speedFieldName) director = QgsVectorLayerDirector(network, directionField, forwardValue, backwardValue, bothValue, defaultDirection) distUnit = context.project().crs().mapUnits() multiplier = QgsUnitTypes.fromUnitToUnitFactor( distUnit, QgsUnitTypes.DistanceMeters) if strategy == 0: strategy = QgsNetworkDistanceStrategy() else: strategy = QgsNetworkSpeedStrategy(speedField, defaultSpeed, multiplier * 1000.0 / 3600.0) director.addStrategy(strategy) builder = QgsGraphBuilder(network.sourceCrs(), True, tolerance) feedback.pushInfo( QCoreApplication.translate('ServiceAreaFromPoint', 'Building graph…')) snappedPoints = director.makeGraph(builder, [startPoint], feedback) feedback.pushInfo( QCoreApplication.translate('ServiceAreaFromPoint', 'Calculating service area…')) graph = builder.graph() idxStart = graph.findVertex(snappedPoints[0]) tree, cost = QgsGraphAnalyzer.dijkstra(graph, idxStart, 0) vertices = set() points = [] lines = [] for vertex, start_vertex_cost in enumerate(cost): inbound_edge_index = tree[vertex] if inbound_edge_index == -1 and vertex != idxStart: # unreachable vertex continue if start_vertex_cost > travelCost: # vertex is too expensive, discard continue vertices.add(vertex) start_point = graph.vertex(vertex).point() # find all edges coming from this vertex for edge_id in graph.vertex(vertex).outgoingEdges(): edge = graph.edge(edge_id) end_vertex_cost = start_vertex_cost + edge.cost(0) end_point = graph.vertex(edge.toVertex()).point() if end_vertex_cost <= travelCost: # end vertex is cheap enough to include vertices.add(edge.toVertex()) lines.append([start_point, end_point]) else: # travelCost sits somewhere on this edge, interpolate position interpolated_end_point = QgsGeometryUtils.interpolatePointOnLineByValue( start_point.x(), start_point.y(), start_vertex_cost, end_point.x(), end_point.y(), end_vertex_cost, travelCost) points.append(interpolated_end_point) lines.append([start_point, interpolated_end_point]) for i in vertices: points.append(graph.vertex(i).point()) feedback.pushInfo( QCoreApplication.translate('ServiceAreaFromPoint', 'Writing results…')) fields = QgsFields() fields.append(QgsField('type', QVariant.String, '', 254, 0)) fields.append(QgsField('start', QVariant.String, '', 254, 0)) feat = QgsFeature() feat.setFields(fields) (point_sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, QgsWkbTypes.MultiPoint, network.sourceCrs()) results = {} if point_sink is not None: results[self.OUTPUT] = dest_id geomPoints = QgsGeometry.fromMultiPointXY(points) feat.setGeometry(geomPoints) feat['type'] = 'within' feat['start'] = startPoint.toString() point_sink.addFeature(feat, QgsFeatureSink.FastInsert) if include_bounds: upperBoundary = [] lowerBoundary = [] vertices = [] for i, v in enumerate(cost): if v > travelCost and tree[i] != -1: vertexId = graph.edge(tree[i]).fromVertex() if cost[vertexId] <= travelCost: vertices.append(i) for i in vertices: upperBoundary.append( graph.vertex(graph.edge(tree[i]).toVertex()).point()) lowerBoundary.append( graph.vertex(graph.edge(tree[i]).fromVertex()).point()) geomUpper = QgsGeometry.fromMultiPointXY(upperBoundary) geomLower = QgsGeometry.fromMultiPointXY(lowerBoundary) feat.setGeometry(geomUpper) feat['type'] = 'upper' feat['start'] = startPoint.toString() point_sink.addFeature(feat, QgsFeatureSink.FastInsert) feat.setGeometry(geomLower) feat['type'] = 'lower' feat['start'] = startPoint.toString() point_sink.addFeature(feat, QgsFeatureSink.FastInsert) (line_sink, line_dest_id) = self.parameterAsSink(parameters, self.OUTPUT_LINES, context, fields, QgsWkbTypes.MultiLineString, network.sourceCrs()) if line_sink is not None: results[self.OUTPUT_LINES] = line_dest_id geom_lines = QgsGeometry.fromMultiPolylineXY(lines) feat.setGeometry(geom_lines) feat['type'] = 'lines' feat['start'] = startPoint.toString() line_sink.addFeature(feat, QgsFeatureSink.FastInsert) return results
def processAlgorithm(self, parameters, context, feedback): database = self.parameterAsVectorLayer(parameters, self.DATABASE, context) databaseuri = database.dataProvider().dataSourceUri() uri = QgsDataSourceUri(databaseuri) if uri.database() is '': if '|layername' in databaseuri: databaseuri = databaseuri[:databaseuri.find('|layername')] elif '|layerid' in databaseuri: databaseuri = databaseuri[:databaseuri.find('|layerid')] uri = QgsDataSourceUri('dbname=\'%s\'' % (databaseuri)) db = spatialite.GeoDB(uri) overwrite = self.parameterAsBool(parameters, self.OVERWRITE, context) createIndex = self.parameterAsBool(parameters, self.CREATEINDEX, context) convertLowerCase = self.parameterAsBool(parameters, self.LOWERCASE_NAMES, context) dropStringLength = self.parameterAsBool(parameters, self.DROP_STRING_LENGTH, context) forceSinglePart = self.parameterAsBool(parameters, self.FORCE_SINGLEPART, context) primaryKeyField = self.parameterAsString(parameters, self.PRIMARY_KEY, context) or 'id' encoding = self.parameterAsString(parameters, self.ENCODING, context) source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT)) table = self.parameterAsString(parameters, self.TABLENAME, context) if table: table.strip() if not table or table == '': table = source.sourceName() table = table.replace('.', '_') table = table.replace(' ', '').lower() providerName = 'spatialite' geomColumn = self.parameterAsString(parameters, self.GEOMETRY_COLUMN, context) if not geomColumn: geomColumn = 'geom' options = {} if overwrite: options['overwrite'] = True if convertLowerCase: options['lowercaseFieldNames'] = True geomColumn = geomColumn.lower() if dropStringLength: options['dropStringConstraints'] = True if forceSinglePart: options['forceSinglePartGeometryType'] = True # Clear geometry column for non-geometry tables if source.wkbType() == QgsWkbTypes.NoGeometry: geomColumn = None uri = db.uri uri.setDataSource('', table, geomColumn, '', primaryKeyField) if encoding: options['fileEncoding'] = encoding exporter = QgsVectorLayerExporter(uri.uri(), providerName, source.fields(), source.wkbType(), source.sourceCrs(), overwrite, options) if exporter.errorCode() != QgsVectorLayerExporter.NoError: raise QgsProcessingException( self.tr('Error importing to Spatialite\n{0}').format( exporter.errorMessage())) features = source.getFeatures() total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, f in enumerate(features): if feedback.isCanceled(): break if not exporter.addFeature(f, QgsFeatureSink.FastInsert): feedback.reportError(exporter.errorMessage()) feedback.setProgress(int(current * total)) exporter.flushBuffer() if exporter.errorCode() != QgsVectorLayerExporter.NoError: raise QgsProcessingException( self.tr('Error importing to Spatialite\n{0}').format( exporter.errorMessage())) if geomColumn and createIndex: db.create_spatial_index(table, geomColumn) return {}
def getConsoleCommands(self, parameters, context, feedback, executing=True): source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT)) fields = source.fields() ogrLayer, layerName = self.getOgrCompatibleSource( self.INPUT, parameters, context, feedback, executing) geometry = self.parameterAsString(parameters, self.GEOMETRY, context) fieldName = self.parameterAsString(parameters, self.FIELD, context) options = self.parameterAsString(parameters, self.OPTIONS, context) outFile = self.parameterAsOutputLayer(parameters, self.OUTPUT, context) output, outputFormat = GdalUtils.ogrConnectionStringAndFormat( outFile, context) other_fields = [] for f in fields: if f.name() == geometry: continue other_fields.append(f.name()) if other_fields: other_fields = ', {}'.format(','.join(other_fields)) else: other_fields = '' arguments = [] arguments.append(output) arguments.append(ogrLayer) arguments.append('-dialect') arguments.append('sqlite') arguments.append('-sql') tokens = [] if self.parameterAsBool(parameters, self.COUNT_FEATURES, context): tokens.append("COUNT({}) AS count".format(geometry)) if self.parameterAsBool(parameters, self.COMPUTE_AREA, context): tokens.append( "SUM(ST_Area({0})) AS area, ST_Perimeter(ST_Union({0})) AS perimeter" .format(geometry)) statsField = self.parameterAsString(parameters, self.FIELD, context) if statsField and self.parameterAsBool( parameters, self.COMPUTE_STATISTICS, context): tokens.append( "SUM({0}) AS sum, MIN({0}) AS min, MAX({0}) AS max, AVG({0}) AS avg" .format(statsField)) params = ','.join(tokens) if params: if self.parameterAsBool(parameters, self.KEEP_ATTRIBUTES, context): sql = "SELECT ST_Union({}) AS {}{}, {} FROM {} GROUP BY {}".format( geometry, geometry, other_fields, params, layerName, fieldName) else: sql = "SELECT ST_Union({}) AS {}, {}, {} FROM {} GROUP BY {}".format( geometry, geometry, fieldName, params, layerName, fieldName) else: if self.parameterAsBool(parameters, self.KEEP_ATTRIBUTES, context): sql = "SELECT ST_Union({}) AS {}{} FROM {} GROUP BY {}".format( geometry, geometry, other_fields, layerName, fieldName) else: sql = "SELECT ST_Union({}) AS {}, {} FROM {} GROUP BY {}".format( geometry, geometry, fieldName, layerName, fieldName) arguments.append(sql) if self.parameterAsBool(parameters, self.EXPLODE_COLLECTIONS, context): arguments.append('-explodecollections') if options: arguments.append(options) if outputFormat: arguments.append('-f {}'.format(outputFormat)) return [self.commandName(), GdalUtils.escapeAndJoin(arguments)]
def execute_in_place_run(alg, parameters, context=None, feedback=None, raise_exceptions=False): """Executes an algorithm modifying features in-place in the input layer. :param alg: algorithm to run :type alg: QgsProcessingAlgorithm :param parameters: parameters of the algorithm :type parameters: dict :param context: context, defaults to None :type context: QgsProcessingContext, optional :param feedback: feedback, defaults to None :type feedback: QgsProcessingFeedback, optional :param raise_exceptions: useful for testing, if True exceptions are raised, normally exceptions will be forwarded to the feedback :type raise_exceptions: boo, default to False :raises QgsProcessingException: raised when there is no active layer, or it cannot be made editable :return: a tuple with true if success and results :rtype: tuple """ if feedback is None: feedback = QgsProcessingFeedback() if context is None: context = dataobjects.createContext(feedback) # Only feature based algs have sourceFlags try: if alg.sourceFlags( ) & QgsProcessingFeatureSource.FlagSkipGeometryValidityChecks: context.setInvalidGeometryCheck(QgsFeatureRequest.GeometryNoCheck) except AttributeError: pass active_layer = parameters['INPUT'] # Run some checks and prepare the layer for in-place execution by: # - getting the active layer and checking that it is a vector # - making the layer editable if it was not already # - selecting all features if none was selected # - checking in-place support for the active layer/alg/parameters # If one of the check fails and raise_exceptions is True an exception # is raised, else the execution is aborted and the error reported in # the feedback try: if active_layer is None: raise QgsProcessingException(tr("There is not active layer.")) if not isinstance(active_layer, QgsVectorLayer): raise QgsProcessingException( tr("Active layer is not a vector layer.")) if not active_layer.isEditable(): if not active_layer.startEditing(): raise QgsProcessingException( tr("Active layer is not editable (and editing could not be turned on)." )) if not alg.supportInPlaceEdit(active_layer): raise QgsProcessingException( tr("Selected algorithm and parameter configuration are not compatible with in-place modifications." )) except QgsProcessingException as e: if raise_exceptions: raise e QgsMessageLog.logMessage(str(sys.exc_info()[0]), 'Processing', Qgis.Critical) if feedback is not None: feedback.reportError(getattr(e, 'msg', str(e)), fatalError=True) return False, {} if not active_layer.selectedFeatureIds(): active_layer.selectAll() # Make sure we are working on selected features only parameters['INPUT'] = QgsProcessingFeatureSourceDefinition( active_layer.id(), True) parameters['OUTPUT'] = 'memory:' req = QgsFeatureRequest(QgsExpression(r"$id < 0")) req.setFlags(QgsFeatureRequest.NoGeometry) req.setSubsetOfAttributes([]) # Start the execution # If anything goes wrong and raise_exceptions is True an exception # is raised, else the execution is aborted and the error reported in # the feedback try: new_feature_ids = [] active_layer.beginEditCommand(alg.displayName()) # Checks whether the algorithm has a processFeature method if hasattr(alg, 'processFeature'): # in-place feature editing # Make a clone or it will crash the second time the dialog # is opened and run alg = alg.create() if not alg.prepare(parameters, context, feedback): raise QgsProcessingException( tr("Could not prepare selected algorithm.")) # Check again for compatibility after prepare if not alg.supportInPlaceEdit(active_layer): raise QgsProcessingException( tr("Selected algorithm and parameter configuration are not compatible with in-place modifications." )) field_idxs = range(len(active_layer.fields())) iterator_req = QgsFeatureRequest(active_layer.selectedFeatureIds()) iterator_req.setInvalidGeometryCheck( context.invalidGeometryCheck()) feature_iterator = active_layer.getFeatures(iterator_req) step = 100 / len(active_layer.selectedFeatureIds() ) if active_layer.selectedFeatureIds() else 1 for current, f in enumerate(feature_iterator): feedback.setProgress(current * step) if feedback.isCanceled(): break # need a deep copy, because python processFeature implementations may return # a shallow copy from processFeature input_feature = QgsFeature(f) new_features = alg.processFeature(input_feature, context, feedback) new_features = QgsVectorLayerUtils.makeFeaturesCompatible( new_features, active_layer) if len(new_features) == 0: active_layer.deleteFeature(f.id()) elif len(new_features) == 1: new_f = new_features[0] if not f.geometry().equals(new_f.geometry()): active_layer.changeGeometry(f.id(), new_f.geometry()) if f.attributes() != new_f.attributes(): active_layer.changeAttributeValues( f.id(), dict(zip(field_idxs, new_f.attributes())), dict(zip(field_idxs, f.attributes()))) new_feature_ids.append(f.id()) else: active_layer.deleteFeature(f.id()) # Get the new ids old_ids = set( [f.id() for f in active_layer.getFeatures(req)]) if not active_layer.addFeatures(new_features): raise QgsProcessingException( tr("Error adding processed features back into the layer." )) new_ids = set( [f.id() for f in active_layer.getFeatures(req)]) new_feature_ids += list(new_ids - old_ids) results, ok = {}, True else: # Traditional 'run' with delete and add features cycle # There is no way to know if some features have been skipped # due to invalid geometries if context.invalidGeometryCheck( ) == QgsFeatureRequest.GeometrySkipInvalid: selected_ids = active_layer.selectedFeatureIds() else: selected_ids = [] results, ok = alg.run(parameters, context, feedback) if ok: result_layer = QgsProcessingUtils.mapLayerFromString( results['OUTPUT'], context) # TODO: check if features have changed before delete/add cycle new_features = [] # Check if there are any skipped features if context.invalidGeometryCheck( ) == QgsFeatureRequest.GeometrySkipInvalid: missing_ids = list( set(selected_ids) - set(result_layer.allFeatureIds())) if missing_ids: for f in active_layer.getFeatures( QgsFeatureRequest(missing_ids)): if not f.geometry().isGeosValid(): new_features.append(f) active_layer.deleteFeatures(active_layer.selectedFeatureIds()) for f in result_layer.getFeatures(): new_features.extend( QgsVectorLayerUtils.makeFeaturesCompatible( [f], active_layer)) # Get the new ids old_ids = set([f.id() for f in active_layer.getFeatures(req)]) if not active_layer.addFeatures(new_features): raise QgsProcessingException( tr("Error adding processed features back into the layer." )) new_ids = set([f.id() for f in active_layer.getFeatures(req)]) new_feature_ids += list(new_ids - old_ids) active_layer.endEditCommand() if ok and new_feature_ids: active_layer.selectByIds(new_feature_ids) elif not ok: active_layer.rollBack() return ok, results except QgsProcessingException as e: active_layer.endEditCommand() active_layer.rollBack() if raise_exceptions: raise e QgsMessageLog.logMessage(str(sys.exc_info()[0]), 'Processing', Qgis.Critical) if feedback is not None: feedback.reportError(getattr(e, 'msg', str(e)), fatalError=True) return False, {}
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT)) radius = self.parameterAsDouble(parameters, self.RADIUS, context) kernel_shape = self.parameterAsEnum(parameters, self.KERNEL, context) pixel_size = self.parameterAsDouble(parameters, self.PIXEL_SIZE, context) decay = self.parameterAsDouble(parameters, self.DECAY, context) output_values = self.parameterAsEnum(parameters, self.OUTPUT_VALUE, context) outputFile = self.parameterAsOutputLayer(parameters, self.OUTPUT, context) output_format = QgsRasterFileWriter.driverForExtension( os.path.splitext(outputFile)[1]) weight_field = self.parameterAsString(parameters, self.WEIGHT_FIELD, context) radius_field = self.parameterAsString(parameters, self.RADIUS_FIELD, context) attrs = [] kde_params = QgsKernelDensityEstimation.Parameters() kde_params.source = source kde_params.radius = radius kde_params.pixelSize = pixel_size # radius field if radius_field: kde_params.radiusField = radius_field attrs.append(source.fields().lookupField(radius_field)) # weight field if weight_field: kde_params.weightField = weight_field attrs.append(source.fields().lookupField(weight_field)) kde_params.shape = kernel_shape kde_params.decayRatio = decay kde_params.outputValues = output_values kde = QgsKernelDensityEstimation(kde_params, outputFile, output_format) if kde.prepare() != QgsKernelDensityEstimation.Success: raise QgsProcessingException( self.tr('Could not create destination layer')) request = QgsFeatureRequest() request.setSubsetOfAttributes(attrs) features = source.getFeatures(request) total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, f in enumerate(features): if feedback.isCanceled(): break if kde.addFeature(f) != QgsKernelDensityEstimation.Success: feedback.reportError( self.tr( 'Error adding feature with ID {} to heatmap').format( f.id())) feedback.setProgress(int(current * total)) if kde.finalise() != QgsKernelDensityEstimation.Success: raise QgsProcessingException( self.tr('Could not save destination layer')) return {self.OUTPUT: outputFile}
def processAlgorithm(self, parameters, context, feedback): spacing = self.parameterAsDouble(parameters, self.SPACING, context) inset = self.parameterAsDouble(parameters, self.INSET, context) randomize = self.parameterAsBool(parameters, self.RANDOMIZE, context) isSpacing = self.parameterAsBool(parameters, self.IS_SPACING, context) crs = self.parameterAsCrs(parameters, self.CRS, context) extent = self.parameterAsExtent(parameters, self.EXTENT, context, crs) fields = QgsFields() fields.append(QgsField('id', QVariant.Int, '', 10, 0)) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, QgsWkbTypes.Point, crs) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) if randomize: seed() area = extent.width() * extent.height() if isSpacing: pSpacing = spacing else: pSpacing = sqrt(area / spacing) f = QgsFeature() f.initAttributes(1) f.setFields(fields) count = 0 id = 0 total = 100.0 / (area / pSpacing) y = extent.yMaximum() - inset extent_geom = QgsGeometry.fromRect(extent) extent_engine = QgsGeometry.createGeometryEngine( extent_geom.constGet()) extent_engine.prepareGeometry() while y >= extent.yMinimum(): x = extent.xMinimum() + inset while x <= extent.xMaximum(): if feedback.isCanceled(): break if randomize: geom = QgsGeometry( QgsPoint( uniform(x - (pSpacing / 2.0), x + (pSpacing / 2.0)), uniform(y - (pSpacing / 2.0), y + (pSpacing / 2.0)))) else: geom = QgsGeometry(QgsPoint(x, y)) if extent_engine.intersects(geom.constGet()): f.setAttributes([id]) f.setGeometry(geom) sink.addFeature(f, QgsFeatureSink.FastInsert) x += pSpacing id += 1 count += 1 feedback.setProgress(int(count * total)) y = y - pSpacing return {self.OUTPUT: dest_id}
def processAlgorithm(self, parameters, context, feedback): network = self.parameterAsSource(parameters, self.INPUT, context) startPoint = self.parameterAsPoint(parameters, self.START_POINT, context, network.sourceCrs()) endPoint = self.parameterAsPoint(parameters, self.END_POINT, context, network.sourceCrs()) strategy = self.parameterAsEnum(parameters, self.STRATEGY, context) directionFieldName = self.parameterAsString(parameters, self.DIRECTION_FIELD, context) forwardValue = self.parameterAsString(parameters, self.VALUE_FORWARD, context) backwardValue = self.parameterAsString(parameters, self.VALUE_BACKWARD, context) bothValue = self.parameterAsString(parameters, self.VALUE_BOTH, context) defaultDirection = self.parameterAsEnum(parameters, self.DEFAULT_DIRECTION, context) speedFieldName = self.parameterAsString(parameters, self.SPEED_FIELD, context) defaultSpeed = self.parameterAsDouble(parameters, self.DEFAULT_SPEED, context) tolerance = self.parameterAsDouble(parameters, self.TOLERANCE, context) fields = QgsFields() fields.append(QgsField('start', QVariant.String, '', 254, 0)) fields.append(QgsField('end', QVariant.String, '', 254, 0)) fields.append(QgsField('cost', QVariant.Double, '', 20, 7)) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, QgsWkbTypes.LineString, network.sourceCrs()) directionField = -1 if directionField: directionField = network.fields().lookupField(directionFieldName) speedField = -1 if speedFieldName: speedField = network.fields().lookupField(speedFieldName) director = QgsVectorLayerDirector(network, directionField, forwardValue, backwardValue, bothValue, defaultDirection) distUnit = context.project().crs().mapUnits() multiplier = QgsUnitTypes.fromUnitToUnitFactor( distUnit, QgsUnitTypes.DistanceMeters) if strategy == 0: strategy = QgsNetworkDistanceStrategy() else: strategy = QgsNetworkSpeedStrategy(speedField, defaultSpeed, multiplier * 1000.0 / 3600.0) multiplier = 3600 director.addStrategy(strategy) builder = QgsGraphBuilder(network.sourceCrs(), True, tolerance) feedback.pushInfo( QCoreApplication.translate('ShortestPathPointToPoint', 'Building graph…')) snappedPoints = director.makeGraph(builder, [startPoint, endPoint], feedback) feedback.pushInfo( QCoreApplication.translate('ShortestPathPointToPoint', 'Calculating shortest path…')) graph = builder.graph() idxStart = graph.findVertex(snappedPoints[0]) idxEnd = graph.findVertex(snappedPoints[1]) tree, costs = QgsGraphAnalyzer.dijkstra(graph, idxStart, 0) if tree[idxEnd] == -1: raise QgsProcessingException( self.tr('There is no route from start point to end point.')) route = [graph.vertex(idxEnd).point()] cost = costs[idxEnd] current = idxEnd while current != idxStart: current = graph.edge(tree[current]).fromVertex() route.append(graph.vertex(current).point()) route.reverse() feedback.pushInfo( QCoreApplication.translate('ShortestPathPointToPoint', 'Writing results…')) geom = QgsGeometry.fromPolylineXY(route) feat = QgsFeature() feat.setFields(fields) feat['start'] = startPoint.toString() feat['end'] = endPoint.toString() feat['cost'] = cost / multiplier feat.setGeometry(geom) sink.addFeature(feat, QgsFeatureSink.FastInsert) results = {} results[self.TRAVEL_COST] = cost / multiplier results[self.OUTPUT] = dest_id return results
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT)) value_field_name = self.parameterAsString(parameters, self.VALUES_FIELD_NAME, context) category_field_names = self.parameterAsFields( parameters, self.CATEGORIES_FIELD_NAME, context) value_field_index = source.fields().lookupField(value_field_name) if value_field_index >= 0: value_field = source.fields().at(value_field_index) else: value_field = None category_field_indexes = [ source.fields().lookupField(n) for n in category_field_names ] # generate output fields fields = QgsFields() for c in category_field_indexes: fields.append(source.fields().at(c)) def addField(name): """ Adds a field to the output, keeping the same data type as the value_field """ field = QgsField(value_field) field.setName(name) fields.append(field) if value_field is None: field_type = 'none' fields.append(QgsField('count', QVariant.Int)) elif value_field.isNumeric(): field_type = 'numeric' fields.append(QgsField('count', QVariant.Int)) fields.append(QgsField('unique', QVariant.Int)) fields.append(QgsField('min', QVariant.Double)) fields.append(QgsField('max', QVariant.Double)) fields.append(QgsField('range', QVariant.Double)) fields.append(QgsField('sum', QVariant.Double)) fields.append(QgsField('mean', QVariant.Double)) fields.append(QgsField('median', QVariant.Double)) fields.append(QgsField('stddev', QVariant.Double)) fields.append(QgsField('minority', QVariant.Double)) fields.append(QgsField('majority', QVariant.Double)) fields.append(QgsField('q1', QVariant.Double)) fields.append(QgsField('q3', QVariant.Double)) fields.append(QgsField('iqr', QVariant.Double)) elif value_field.type() in (QVariant.Date, QVariant.Time, QVariant.DateTime): field_type = 'datetime' fields.append(QgsField('count', QVariant.Int)) fields.append(QgsField('unique', QVariant.Int)) fields.append(QgsField('empty', QVariant.Int)) fields.append(QgsField('filled', QVariant.Int)) # keep same data type for these fields addField('min') addField('max') else: field_type = 'string' fields.append(QgsField('count', QVariant.Int)) fields.append(QgsField('unique', QVariant.Int)) fields.append(QgsField('empty', QVariant.Int)) fields.append(QgsField('filled', QVariant.Int)) # keep same data type for these fields addField('min') addField('max') fields.append(QgsField('min_length', QVariant.Int)) fields.append(QgsField('max_length', QVariant.Int)) fields.append(QgsField('mean_length', QVariant.Double)) request = QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry) if value_field is not None: attrs = [value_field_index] else: attrs = [] attrs.extend(category_field_indexes) request.setSubsetOfAttributes(attrs) features = source.getFeatures( request, QgsProcessingFeatureSource.FlagSkipGeometryValidityChecks) total = 50.0 / source.featureCount() if source.featureCount() else 0 if field_type == 'none': values = defaultdict(lambda: 0) else: values = defaultdict(list) for current, feat in enumerate(features): if feedback.isCanceled(): break feedback.setProgress(int(current * total)) attrs = feat.attributes() cat = tuple([attrs[c] for c in category_field_indexes]) if field_type == 'none': values[cat] += 1 continue if field_type == 'numeric': if attrs[value_field_index] == NULL: continue else: value = float(attrs[value_field_index]) elif field_type == 'string': if attrs[value_field_index] == NULL: value = '' else: value = str(attrs[value_field_index]) elif attrs[value_field_index] == NULL: value = NULL else: value = attrs[value_field_index] values[cat].append(value) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, QgsWkbTypes.NoGeometry, QgsCoordinateReferenceSystem()) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) if field_type == 'none': self.saveCounts(values, sink, feedback) elif field_type == 'numeric': self.calcNumericStats(values, sink, feedback) elif field_type == 'datetime': self.calcDateTimeStats(values, sink, feedback) else: self.calcStringStats(values, sink, feedback) return {self.OUTPUT: dest_id}
def mergeDataSources2Vrt(self, dataSources, outFile, union=False, relative=False, schema=False, feedback=None): '''Function to do the work of merging datasources in a single vrt format @param data_sources: Array of path strings @param outfile: the output vrt file to generate @param relative: Write relative flag. DOES NOT relativise paths. They have to be already relative @param schema: Schema flag @return: vrt in string format ''' if feedback is None: feedback = QgsProcessingFeedback() vrt = '<OGRVRTDataSource>' if union: vrt += '<OGRVRTUnionLayer name="UnionedLayer">' total = 100.0 / len(dataSources) if dataSources else 1 for current, layer in enumerate(dataSources): if feedback.isCanceled(): break feedback.setProgress(int(current * total)) inFile = layer.source() srcDS = ogr.Open(inFile, 0) if srcDS is None: raise QgsProcessingException( self.tr('Invalid datasource: {}'.format(inFile))) if schema: inFile = '@dummy@' for layer in srcDS: layerDef = layer.GetLayerDefn() layerName = layerDef.GetName() vrt += '<OGRVRTLayer name="{}">'.format(self.XmlEsc(layerName)) vrt += '<SrcDataSource relativeToVRT="{}" shared="{}">{}</SrcDataSource>'.format(1 if relative else 0, not schema, self.XmlEsc(inFile)) if schema: vrt += '<SrcLayer>@dummy@</SrcLayer>' else: vrt += '<SrcLayer>{}</SrcLayer>'.format(self.XmlEsc(layerName)) vrt += '<GeometryType>{}</GeometryType>'.format(self.GeomType2Name(layerDef.GetGeomType())) crs = layer.GetSpatialRef() if crs is not None: vrt += '<LayerSRS>{}</LayerSRS>'.format(self.XmlEsc(crs.ExportToWkt())) # Process all the fields. for fieldIdx in range(layerDef.GetFieldCount()): fieldDef = layerDef.GetFieldDefn(fieldIdx) vrt += '<Field name="{}" type="{}"'.format(self.XmlEsc(fieldDef.GetName()), self.fieldType2Name(fieldDef.GetType())) if not schema: vrt += ' src="{}"'.format(self.XmlEsc(fieldDef.GetName())) if fieldDef.GetWidth() > 0: vrt += ' width="{}"'.format(fieldDef.GetWidth()) if fieldDef.GetPrecision() > 0: vrt += ' precision="{}"'.format(fieldDef.GetPrecision()) vrt += '/>' vrt += '</OGRVRTLayer>' srcDS.Destroy() if union: vrt += '</OGRVRTUnionLayer>' vrt += '</OGRVRTDataSource>' #TODO: pretty-print XML if outFile is not None: with codecs.open(outFile, 'w') as f: f.write(vrt) return vrt
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT)) proximity = self.parameterAsDouble(parameters, self.PROXIMITY, context) radius = self.parameterAsDouble(parameters, self.DISTANCE, context) horizontal = self.parameterAsBool(parameters, self.HORIZONTAL, context) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, source.fields(), source.wkbType(), source.sourceCrs()) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) features = source.getFeatures() total = 100.0 / source.featureCount() if source.featureCount() else 0 def searchRect(p): return QgsRectangle(p.x() - proximity, p.y() - proximity, p.x() + proximity, p.y() + proximity) index = QgsSpatialIndex() # NOTE: this is a Python port of QgsPointDistanceRenderer::renderFeature. If refining this algorithm, # please port the changes to QgsPointDistanceRenderer::renderFeature also! clustered_groups = [] group_index = {} group_locations = {} for current, f in enumerate(features): if feedback.isCanceled(): break if not f.hasGeometry(): continue point = f.geometry().asPoint() other_features_within_radius = index.intersects(searchRect(point)) if not other_features_within_radius: index.insertFeature(f) group = [f] clustered_groups.append(group) group_index[f.id()] = len(clustered_groups) - 1 group_locations[f.id()] = point else: # find group with closest location to this point (may be more than one within search tolerance) min_dist_feature_id = other_features_within_radius[0] min_dist = group_locations[min_dist_feature_id].distance(point) for i in range(1, len(other_features_within_radius)): candidate_id = other_features_within_radius[i] new_dist = group_locations[candidate_id].distance(point) if new_dist < min_dist: min_dist = new_dist min_dist_feature_id = candidate_id group_index_pos = group_index[min_dist_feature_id] group = clustered_groups[group_index_pos] # calculate new centroid of group old_center = group_locations[min_dist_feature_id] group_locations[min_dist_feature_id] = QgsPointXY( (old_center.x() * len(group) + point.x()) / (len(group) + 1.0), (old_center.y() * len(group) + point.y()) / (len(group) + 1.0)) # add to a group clustered_groups[group_index_pos].append(f) group_index[f.id()] = group_index_pos feedback.setProgress(int(current * total)) current = 0 total = 100.0 / len(clustered_groups) if clustered_groups else 1 feedback.setProgress(0) fullPerimeter = 2 * math.pi for group in clustered_groups: if feedback.isCanceled(): break count = len(group) if count == 1: sink.addFeature(group[0], QgsFeatureSink.FastInsert) else: angleStep = fullPerimeter / count if count == 2 and horizontal: currentAngle = math.pi / 2 else: currentAngle = 0 old_point = group_locations[group[0].id()] for f in group: if feedback.isCanceled(): break sinusCurrentAngle = math.sin(currentAngle) cosinusCurrentAngle = math.cos(currentAngle) dx = radius * sinusCurrentAngle dy = radius * cosinusCurrentAngle # we want to keep any existing m/z values point = f.geometry().constGet().clone() point.setX(old_point.x() + dx) point.setY(old_point.y() + dy) f.setGeometry(QgsGeometry(point)) sink.addFeature(f, QgsFeatureSink.FastInsert) currentAngle += angleStep current += 1 feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
def runAlgorithm(algOrName, parameters, onFinish=None, feedback=None, context=None): if isinstance(algOrName, QgsProcessingAlgorithm): alg = algOrName else: alg = QgsApplication.processingRegistry().createAlgorithmById(algOrName) if feedback is None: feedback = MessageBarProgress(alg.displayName() if alg else Processing.tr('Processing')) if alg is None: # fix_print_with_import print('Error: Algorithm not found\n') msg = Processing.tr('Error: Algorithm {0} not found\n').format(algOrName) feedback.reportError(msg) raise QgsProcessingException(msg) # check for any mandatory parameters which were not specified for param in alg.parameterDefinitions(): if param.name() not in parameters: if not param.flags() & QgsProcessingParameterDefinition.FlagOptional: # fix_print_with_import msg = Processing.tr('Error: Missing parameter value for parameter {0}.').format(param.name()) print('Error: Missing parameter value for parameter %s.' % param.name()) feedback.reportError(msg) raise QgsProcessingException(msg) if context is None: context = dataobjects.createContext(feedback) ok, msg = alg.checkParameterValues(parameters, context) if not ok: # fix_print_with_import print('Unable to execute algorithm\n' + str(msg)) msg = Processing.tr('Unable to execute algorithm\n{0}').format(msg) feedback.reportError(msg) raise QgsProcessingException(msg) if not alg.validateInputCrs(parameters, context): print('Warning: Not all input layers use the same CRS.\n' + 'This can cause unexpected results.') feedback.pushInfo( Processing.tr('Warning: Not all input layers use the same CRS.\nThis can cause unexpected results.')) ret, results = execute(alg, parameters, context, feedback) if ret: feedback.pushInfo( Processing.tr('Results: {}').format(results)) if onFinish is not None: onFinish(alg, context, feedback) else: # auto convert layer references in results to map layers for out in alg.outputDefinitions(): if isinstance(out, (QgsProcessingOutputVectorLayer, QgsProcessingOutputRasterLayer, QgsProcessingOutputMapLayer)): result = results[out.name()] if not isinstance(result, QgsMapLayer): layer = context.takeResultLayer(result) # transfer layer ownership out of context if layer: results[out.name()] = layer # replace layer string ref with actual layer (+ownership) elif isinstance(out, QgsProcessingOutputMultipleLayers): result = results[out.name()] if result: layers_result = [] for l in result: if not isinstance(result, QgsMapLayer): layer = context.takeResultLayer(l) # transfer layer ownership out of context if layer: layers_result.append(layer) else: layers_result.append(l) else: layers_result.append(l) results[out.name()] = layers_result # replace layers strings ref with actual layers (+ownership) else: msg = Processing.tr("There were errors executing the algorithm.") feedback.reportError(msg) raise QgsProcessingException(msg) if isinstance(feedback, MessageBarProgress): feedback.close() return results
def processAlgorithm(self, parameters, context, feedback): # Get variables from dialog source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT)) field_name = self.parameterAsString(parameters, self.FIELD, context) kneighbors = self.parameterAsInt(parameters, self.KNEIGHBORS, context) use_field = bool(field_name) field_index = -1 fields = QgsFields() fields.append(QgsField('id', QVariant.Int, '', 20)) current = 0 # Get properties of the field the grouping is based on if use_field: field_index = source.fields().lookupField(field_name) if field_index >= 0: fields.append( source.fields()[field_index] ) # Add a field with the name of the grouping field # Initialize writer (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, QgsWkbTypes.Polygon, source.sourceCrs()) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) success = False fid = 0 # Get unique values of grouping field unique_values = source.uniqueValues(field_index) total = 100.0 / float( source.featureCount() * len(unique_values)) for unique in unique_values: points = [] filter = QgsExpression.createFieldEqualityExpression( field_name, unique) request = QgsFeatureRequest().setFilterExpression(filter) request.setSubsetOfAttributes([]) # Get features with the grouping attribute equal to the current grouping value features = source.getFeatures(request) for in_feature in features: if feedback.isCanceled(): break # Add points or vertices of more complex geometry points.extend(extract_points(in_feature.geometry())) current += 1 feedback.setProgress(int(current * total)) # A minimum of 3 points is necessary to proceed if len(points) >= 3: out_feature = QgsFeature() the_hull = concave_hull(points, kneighbors) if the_hull: vertex = [ QgsPointXY(point[0], point[1]) for point in the_hull ] poly = QgsGeometry().fromPolygonXY([vertex]) out_feature.setGeometry(poly) # Give the polygon the same attribute as the point grouping attribute out_feature.setAttributes([fid, unique]) sink.addFeature(out_feature, QgsFeatureSink.FastInsert) success = True # at least one polygon created fid += 1 if not success: raise QgsProcessingException( 'No hulls could be created. Most likely there were not at least three unique points in any of the groups.' ) else: # Field parameter provided but can't read from it raise QgsProcessingException('Unable to find grouping field') else: # Not grouped by field # Initialize writer (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, QgsWkbTypes.Polygon, source.sourceCrs()) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) points = [] request = QgsFeatureRequest() request.setSubsetOfAttributes([]) features = source.getFeatures(request) # Get all features total = 100.0 / source.featureCount() if source.featureCount( ) else 0 for in_feature in features: if feedback.isCanceled(): break # Add points or vertices of more complex geometry points.extend(extract_points(in_feature.geometry())) current += 1 feedback.setProgress(int(current * total)) # A minimum of 3 points is necessary to proceed if len(points) >= 3: out_feature = QgsFeature() the_hull = concave_hull(points, kneighbors) if the_hull: vertex = [ QgsPointXY(point[0], point[1]) for point in the_hull ] poly = QgsGeometry().fromPolygonXY([vertex]) out_feature.setGeometry(poly) out_feature.setAttributes([0]) sink.addFeature(out_feature, QgsFeatureSink.FastInsert) else: # the_hull returns None only when there are less than three points after cleaning raise QgsProcessingException( 'At least three unique points are required to create a concave hull.' ) else: raise QgsProcessingException( 'At least three points are required to create a concave hull.' ) return {self.OUTPUT: dest_id}
def processAlgorithm(self, parameters, context, feedback): uavImage = self.parameterAsRasterLayer(parameters, self.INPUT, context) feedback.pushInfo(self.tr('Processing image source: ')+self.tr(uavImage.source())) sourceCRS = self.parameterAsCrs(parameters, self.SOURCE_CRS, context) if sourceCRS is None or not sourceCRS.isValid(): sourceCRS = uavImage.crs() destinationCRS = self.parameterAsCrs(parameters, self.DESTINATION_CRS, context) if destinationCRS is None or not destinationCRS.isValid(): feedback.pushInfo(self.tr('Getting destination CRS from source image')) destinationCRS = sourceCRS feedback.pushInfo(self.tr('Source CRS is: ')+self.tr(sourceCRS.authid())) feedback.pushInfo(self.tr('Destination CRS is: ')+self.tr(destinationCRS.authid())) # set fields for footprint and nadir vectors fields = QgsFields() # fields.append(QgsField('gimball_pitch', QVariant.Double)) # fields.append(QgsField('gimball_roll', QVariant.Double)) # fields.append(QgsField('gimball_jaw', QVariant.Double)) # fields.append(QgsField('relative_altitude', QVariant.Double)) # fields.append(QgsField('image', QVariant.String)) # fields.append(QgsField('camera_model', QVariant.String)) # fields.append(QgsField('camera_vertical_FOV', QVariant.Double)) # fields.append(QgsField('camera_horizontal_FOV', QVariant.Double)) # (footprintSink, footprint_dest_id) = self.parameterAsSink( # parameters, # self.OUTPUT_FOOTPRINT, # context, # fields, # QgsWkbTypes.Polygon, # destinationCRS) # if footprintSink is None: # raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT_FOOTPRINT)) (nadirSink, nadir_dest_id) = self.parameterAsSink( parameters, self.OUTPUT_NADIR, context, fields, QgsWkbTypes.Point, destinationCRS) if nadirSink is None: raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT_NADIR)) horizontalFOV = self.parameterAsDouble(parameters, self.HORIZONTAL_FOV, context) verticalFOV = self.parameterAsDouble(parameters, self.VERTICAL_FOV, context) useImageRatio = self.parameterAsBoolean(parameters, self.USE_IMAGE_RATIO_FOR_VERTICAL_FOV, context) verticalFOV_multiplier = self.parameterAsDouble(parameters, self.VERTICAL_FOV_MULTIPLIER, context) nadirToBottomOffset = self.parameterAsDouble(parameters, self.NADIR_TO_BOTTOM_OFFSET, context) nadirToupperOffset = self.parameterAsDouble(parameters, self.NADIR_TO_UPPPER_OFFSET, context) # extract exif and XMP data try: gdal.UseExceptions() dataFrame = gdal.Open(uavImage.source(), gdal.GA_ReadOnly) domains = dataFrame.GetMetadataDomainList() # get exif metadata exifTags = dataFrame.GetMetadata() for key, value in exifTags.items(): #print(key, ':', value) pass # select metadata from XMP domain only droneMetadata = {} for domain in domains: metadata = dataFrame.GetMetadata(domain) # skip not relevant tags if isinstance(metadata, dict): for key, value in metadata.items(): #print(domain, "--", key, ":", value) pass # probably XMPs if isinstance(metadata, list): if domain == 'xml:XMP': # parse xml root = ElementTree.XML(metadata[0]) xmldict = XmlDictConfig(root) # skip first element containing only description and domain info subdict = list(xmldict.values())[0] # get XMP tags subdict = list(subdict.values())[0] # parse XMP stuffs removing head namespace in the key # e.g. # {http://www.dji.com/drone-dji/1.0/}AbsoluteAltitude # become # AbsoluteAltitude for key, value in subdict.items(): #print(domain, '--', key, value) key = key.split('}')[1] droneMetadata[key] = value except Exception as ex: raise QgsProcessingException(str(ex)) # extract all important tagged information about the image # get image lat/lon that will be the coordinates of nadir point # converted to destination CRS lat = _convert_to_degress(exifTags['EXIF_GPSLatitude']) emisphere = exifTags['EXIF_GPSLatitudeRef'] lon = _convert_to_degress(exifTags['EXIF_GPSLongitude']) lonReference = exifTags['EXIF_GPSLongitudeRef'] if emisphere == 'S': lat = -lat if lonReference == 'W': lon = -lon exifDateTime = exifTags['EXIF_DateTime'] feedback.pushInfo("EXIF_DateTime: "+exifDateTime) exifImageWidth = exifTags['EXIF_PixelXDimension'] exifImageLength = exifTags['EXIF_PixelYDimension'] imageRatio = float(exifImageWidth)/float(exifImageLength) feedback.pushInfo("EXIF_PixelXDimension: "+exifImageWidth) feedback.pushInfo("EXIF_PixelYDimension: "+exifImageLength) feedback.pushInfo("Image ratio: "+str(imageRatio)) # drone especific metadata droneMaker = exifTags['EXIF_Make'] droneModel = exifTags['EXIF_Model'] feedback.pushInfo("EXIF_Make: "+droneMaker) feedback.pushInfo("EXIF_Model: "+droneModel) # drone maker substitute XMP drone dictKey dictKey = droneMaker relativeAltitude = float(droneMetadata['RelativeAltitude']) feedback.pushInfo(self.tr("XMP {}:RelativeAltitude: ".format(dictKey))+str(relativeAltitude)) gimballRoll = float(droneMetadata['GimbalRollDegree']) gimballPitch = float(droneMetadata['GimbalPitchDegree']) gimballYaw = float(droneMetadata['GimbalYawDegree']) feedback.pushInfo("XMP {}:GimbalRollDegree: ".format(dictKey)+str(gimballRoll)) feedback.pushInfo("XMP {}:GimbalPitchDegree: ".format(dictKey)+str(gimballPitch)) feedback.pushInfo("XMP {}:GimbalYawDegree: ".format(dictKey)+str(gimballYaw)) flightRoll = float(droneMetadata['FlightRollDegree']) flightPitch = float(droneMetadata['FlightPitchDegree']) flightYaw = float(droneMetadata['FlightYawDegree']) feedback.pushInfo("XMP {}:FlightRollDegree: ".format(dictKey)+str(flightRoll)) feedback.pushInfo("XMP {}:FlightPitchDegree: ".format(dictKey)+str(flightPitch)) feedback.pushInfo("XMP {}:FlightYawDegree: ".format(dictKey)+str(flightYaw)) feedback.pushInfo(self.tr("Horizontal FOV: ")+str(horizontalFOV)) if useImageRatio: verticalFOV = (horizontalFOV/imageRatio)*verticalFOV_multiplier feedback.pushInfo(self.tr("Vertical FOV basing on image ratio: ")+str(verticalFOV)) else: feedback.pushInfo(self.tr("Vertical FOV: ")+str(verticalFOV)) # do calculation inspired by: # https://photo.stackexchange.com/questions/56596/how-do-i-calculate-the-ground-footprint-of-an-aerial-camera # distance of the nearest point to nadir (bottom distance) bottomDistance = relativeAltitude*(math.tan(math.radians(90 - gimballPitch - 0.5*verticalFOV))) # distance of the farest point to nadir (upper distance) upperDistance = relativeAltitude*(math.tan(math.radians(90 - gimballPitch + 0.5*verticalFOV))) feedback.pushInfo(self.tr("Northing (degree): ")+str(gimballYaw)) feedback.pushInfo(self.tr("Nadir to bottom distance (metre): ")+str(bottomDistance)) feedback.pushInfo(self.tr("Nadir to upper distance (metre): ")+str(upperDistance)) # populate nadir layer droneLocation = QgsPoint(lon, lat) tr = QgsCoordinateTransform(sourceCRS, destinationCRS, QgsProject.instance()) droneLocation.transform(tr) feedback.pushInfo(self.tr("Nadir coordinates (lon, lat): ")+'{}, {}'.format(droneLocation.x(), droneLocation.y())) nadirGeometry = QgsGeometry.fromPointXY(QgsPointXY(droneLocation.x(), droneLocation.y())) feature = QgsFeature() feature.setGeometry(nadirGeometry) nadirSink.addFeature(feature, QgsFeatureSink.FastInsert) # create a memory layer with nadir point to be input to "native:wedgebuffers" algorithm # it's not possible to use directly the FeatureSink becaseu can't be accepted by processing.run. # the reason is that a generic sink can be also NOT a layer but a more generic sink where features # can't be recovered. tempNadirLayer = QgsVectorLayer('Point?crs={}'.format(destinationCRS.authid()), 'tempNadirLayer', 'memory' ) provider = tempNadirLayer.dataProvider() feature = QgsFeature() feature.setGeometry(nadirGeometry) provider.addFeatures([feature]) # create polygon using wedge buffer processign algorithm parameters = { 'INPUT': tempNadirLayer, 'AZIMUTH': gimballYaw, 'WIDTH': horizontalFOV, 'OUTER_RADIUS': abs(bottomDistance) + nadirToBottomOffset, 'INNER_RADIUS': abs(upperDistance) + nadirToupperOffset, 'OUTPUT': parameters[self.OUTPUT_FOOTPRINT] } wedge_buffer_result = processing.run("native:wedgebuffers", parameters, is_child_algorithm=True, context = context, feedback = feedback) # Return the results results = { self.OUTPUT_FOOTPRINT: wedge_buffer_result['OUTPUT'], self.OUTPUT_NADIR: nadir_dest_id, } return results
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) field = self.parameterAsString(parameters, self.FIELD, context) index = source.fields().lookupField(field) features = source.getFeatures( QgsFeatureRequest(), QgsProcessingFeatureSource.FlagSkipGeometryValidityChecks) featureCount = source.featureCount() unique = source.uniqueValues(index) value = self.parameterAsInt(parameters, self.NUMBER, context) if method == 0: if value > featureCount: raise QgsProcessingException( self.tr('Selected number is greater that feature count. ' 'Choose lesser value and try again.')) else: if value > 100: raise QgsProcessingException( self.tr("Percentage can't be greater than 100. Set " "correct value and try again.")) value = value / 100.0 (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, source.fields(), source.wkbType(), source.sourceCrs()) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) selran = [] total = 100.0 / (featureCount * len(unique)) if featureCount else 1 classes = defaultdict(list) for i, feature in enumerate(features): if feedback.isCanceled(): break attrs = feature.attributes() classes[attrs[index]].append(feature) feedback.setProgress(int(i * total)) for k, subset in classes.items(): selValue = value if method != 1 else int( round(value * len(subset), 0)) if selValue > len(subset): selValue = len(subset) feedback.reportError( self. tr('Subset "{}" is smaller than requested number of features.' .format(k))) selran.extend(random.sample(subset, selValue)) total = 100.0 / featureCount if featureCount else 1 for (i, feat) in enumerate(selran): if feedback.isCanceled(): break sink.addFeature(feat, QgsFeatureSink.FastInsert) feedback.setProgress(int(i * total)) return {self.OUTPUT: dest_id}
def processAlgorithm(self, original_parameters, context, feedback): if isWindows(): path = Grass7Utils.grassPath() if path == '': raise QgsProcessingException( self.tr( 'GRASS GIS 7 folder is not configured. Please ' 'configure it before running GRASS GIS 7 algorithms.')) # make a copy of the original parameters dictionary - it gets modified by grass algorithms parameters = {k: v for k, v in original_parameters.items()} # Create brand new commands lists self.commands = [] self.outputCommands = [] self.exportedLayers = {} # If GRASS session has been created outside of this algorithm then # get the list of layers loaded in GRASS otherwise start a new # session existingSession = Grass7Utils.sessionRunning if existingSession: self.exportedLayers = Grass7Utils.getSessionLayers() else: Grass7Utils.startGrassSession() # Handle default GRASS parameters self.grabDefaultGrassParameters(parameters, context) # Handle ext functions for inputs/command/outputs for fName in ['Inputs', 'Command', 'Outputs']: fullName = 'process{}'.format(fName) if self.module and hasattr(self.module, fullName): getattr(self.module, fullName)(self, parameters, context, feedback) else: getattr(self, fullName)(parameters, context, feedback) # Run GRASS loglines = [] loglines.append(self.tr('GRASS GIS 7 execution commands')) for line in self.commands: feedback.pushCommandInfo(line) loglines.append(line) if ProcessingConfig.getSetting(Grass7Utils.GRASS_LOG_COMMANDS): QgsMessageLog.logMessage("\n".join(loglines), self.tr('Processing'), Qgis.Info) Grass7Utils.executeGrass(self.commands, feedback, self.outputCommands) # If the session has been created outside of this algorithm, add # the new GRASS GIS 7 layers to it otherwise finish the session if existingSession: Grass7Utils.addSessionLayers(self.exportedLayers) else: Grass7Utils.endGrassSession() # Return outputs map outputs = {} for out in self.outputDefinitions(): outName = out.name() if outName in parameters: outputs[outName] = parameters[outName] if isinstance(out, QgsProcessingOutputHtml): self.convertToHtml(parameters[outName]) return outputs
def processAlgorithm(self, parameters, context, feedback): # Retrieve the feature source and sink. The 'dest_id' variable is used # to uniquely identify the feature sink, and must be included in the # dictionary returned by the processAlgorithm function. source = self.parameterAsSource(parameters, self.INPUT, context) # If source was not found, throw an exception to indicate that the algorithm # encountered a fatal error. The exception text can be any string, but in this # case we use the pre-built invalidSourceError method to return a standard # helper text for when a source cannot be evaluated if source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT)) # SquareId field sq_id_name = self.parameterAsFields(parameters, self.RUTID, context) # Cost_level field costlevelname = self.parameterAsFields(parameters, self.COSTLEVEL, context) # Header rows headerrows = self.parameterAsString(parameters, self.RADER, context) prefix_rutid = self.parameterAsString(parameters, self.PREFIXRUTID, context) # Square sizes sq_size_Index = self.parameterAsEnum(parameters, self.RUTSTORLEK, context) sq_size = self.isoconfig["squaresizes"][sq_size_Index] # Mapping depending on database used dbIndex = self.parameterAsEnum(parameters, self.DATABAS, context) keysDatabaser = [k for k in self.isoconfig["mapDatabases"]] used_databas = keysDatabaser[dbIndex] databas_config = self.isoconfig["mapDatabases"][used_databas] row7_0 = databas_config["NamnRad7_0"] row7_1 = databas_config["NamnRad7_1"] ftable = databas_config["FACTTABLE"] vset_0 = databas_config["VALUESET_0"] vset_1 = databas_config["VALUESET_1"] # Output file textfile = self.parameterAsFileOutput(parameters, self.OUTPUT, context) # Compute the number of steps to display within the progress bar and # get features from source total = 100.0 / source.featureCount() if source.featureCount() else 0 features = source.getFeatures() output_content = [] output_content.append(headerrows) headerrow_7 = 'RECODE "{0}" FROM "{1}{2}{3}" FACTTABLE "{4}"\n'.format( costlevelname[0], row7_0, sq_size, row7_1, ftable) output_content.append('\n') output_content.append(headerrow_7) main_content = '' headerrows_lastpart = [] costlevel_cats = '' for current, feature in enumerate(features): # Stop the algorithm if cancel button has been clicked if feedback.isCanceled(): break f_costVal = feature[costlevelname[0]] f_costValCat = feature[costlevelname[0]] if (f_costVal): if (f_costValCat not in headerrows_lastpart): headerrows_lastpart.append(f_costValCat) sq_id = feature[sq_id_name[0]] if (len(sq_id) <= 13): sq_id = prefix_rutid + sq_id line_string = 'MAP CODE "{0}" VALUESET "{1}{2}{3}" TO "{4}"'.format( sq_id, vset_0, sq_size, vset_1, f_costVal) line_string += '\n' main_content += line_string # Update the progress bar feedback.setProgress(int(current * total)) headerrows_lastpart.sort() for i, row in enumerate(headerrows_lastpart): thestring = 'RESULT "{0}"'.format(headerrows_lastpart[i]) thestring += '\n' costlevel_cats += thestring output_content.append(costlevel_cats) output_content.append(main_content) output_content.append('END RECODE') with open(textfile, 'w') as output_file: output_file.writelines(output_content) return {self.OUTPUT: textfile}
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) fields = QgsFields() fields.append(QgsField('POINTA', QVariant.Double, '', 24, 15)) fields.append(QgsField('POINTB', QVariant.Double, '', 24, 15)) fields.append(QgsField('POINTC', QVariant.Double, '', 24, 15)) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, QgsWkbTypes.Polygon, source.sourceCrs()) pts = [] ptDict = {} ptNdx = -1 c = voronoi.Context() features = source.getFeatures() total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, inFeat in enumerate(features): if feedback.isCanceled(): break geom = QgsGeometry(inFeat.geometry()) if geom.isNull(): continue if geom.isMultipart(): points = geom.asMultiPoint() else: points = [geom.asPoint()] for n, point in enumerate(points): x = point.x() y = point.y() pts.append((x, y)) ptNdx += 1 ptDict[ptNdx] = (inFeat.id(), n) feedback.setProgress(int(current * total)) if len(pts) < 3: raise QgsProcessingException( self.tr('Input file should contain at least 3 points. Choose ' 'another file and try again.')) uniqueSet = set(item for item in pts) ids = [pts.index(item) for item in uniqueSet] sl = voronoi.SiteList([voronoi.Site(*i) for i in uniqueSet]) c.triangulate = True voronoi.voronoi(sl, c) triangles = c.triangles feat = QgsFeature() total = 100.0 / len(triangles) if triangles else 1 for current, triangle in enumerate(triangles): if feedback.isCanceled(): break indices = list(triangle) indices.append(indices[0]) polygon = [] attrs = [] step = 0 for index in indices: fid, n = ptDict[ids[index]] request = QgsFeatureRequest().setFilterFid(fid) inFeat = next(source.getFeatures(request)) geom = QgsGeometry(inFeat.geometry()) if geom.isMultipart(): point = QgsPointXY(geom.asMultiPoint()[n]) else: point = QgsPointXY(geom.asPoint()) polygon.append(point) if step <= 3: attrs.append(ids[index]) step += 1 feat.setAttributes(attrs) geometry = QgsGeometry().fromPolygonXY([polygon]) feat.setGeometry(geometry) sink.addFeature(feat, 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)) field_name = self.parameterAsString(parameters, self.FIELD_NAME, context) field_type = self.TYPES[self.parameterAsEnum(parameters, self.FIELD_TYPE, context)] width = self.parameterAsInt(parameters, self.FIELD_LENGTH, context) precision = self.parameterAsInt(parameters, self.FIELD_PRECISION, context) code = self.parameterAsString(parameters, self.FORMULA, context) globalExpression = self.parameterAsString(parameters, self.GLOBAL, context) fields = source.fields() field = QgsField(field_name, field_type, '', width, precision) fields.append(field) new_ns = {} (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, source.wkbType(), source.sourceCrs()) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) # Run global code if globalExpression.strip() != '': try: bytecode = compile(globalExpression, '<string>', 'exec') exec(bytecode, new_ns) except: raise QgsProcessingException( self. tr("FieldPyculator code execute error.Global code block can't be executed!\n{0}\n{1}" ).format(str(sys.exc_info()[0].__name__), str(sys.exc_info()[1]))) # Replace all fields tags fields = source.fields() num = 0 for field in fields: field_name = str(field.name()) replval = '__attr[' + str(num) + ']' code = code.replace('<' + field_name + '>', replval) num += 1 # Replace all special vars code = code.replace('$id', '__id') code = code.replace('$geom', '__geom') need_id = code.find('__id') != -1 need_geom = code.find('__geom') != -1 need_attrs = code.find('__attr') != -1 # Compile try: bytecode = compile(code, '<string>', 'exec') except: raise QgsProcessingException( self. tr("FieldPyculator code execute error. Field code block can't be executed!\n{0}\n{1}" ).format(str(sys.exc_info()[0].__name__), str(sys.exc_info()[1]))) # Run features = source.getFeatures() total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, feat in enumerate(features): if feedback.isCanceled(): break feedback.setProgress(int(current * total)) attrs = feat.attributes() feat_id = feat.id() # Add needed vars if need_id: new_ns['__id'] = feat_id if need_geom: geom = feat.geometry() new_ns['__geom'] = geom if need_attrs: pyattrs = [a for a in attrs] new_ns['__attr'] = pyattrs # Clear old result if self.RESULT_VAR_NAME in new_ns: del new_ns[self.RESULT_VAR_NAME] # Exec exec(bytecode, new_ns) # Check result if self.RESULT_VAR_NAME not in new_ns: raise QgsProcessingException( self.tr( "FieldPyculator code execute error\n" "Field code block does not return '{0}' variable! " "Please declare this variable in your code!").format( self.RESULT_VAR_NAME)) # Write feature attrs.append(new_ns[self.RESULT_VAR_NAME]) feat.setAttributes(attrs) sink.addFeature(feat, QgsFeatureSink.FastInsert) return {self.OUTPUT: dest_id}
def processAlgorithm(self, parameters, context, feedback): line_source = self.parameterAsSource(parameters, self.LINES, context) if line_source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.LINES)) poly_source = self.parameterAsSource(parameters, self.POLYGONS, context) if poly_source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.POLYGONS)) length_field_name = self.parameterAsString(parameters, self.LEN_FIELD, context) count_field_name = self.parameterAsString(parameters, self.COUNT_FIELD, context) fields = poly_source.fields() if fields.lookupField(length_field_name) < 0: fields.append(QgsField(length_field_name, QVariant.Double)) length_field_index = fields.lookupField(length_field_name) if fields.lookupField(count_field_name) < 0: fields.append(QgsField(count_field_name, QVariant.Int)) count_field_index = fields.lookupField(count_field_name) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, poly_source.wkbType(), poly_source.sourceCrs()) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) spatialIndex = QgsSpatialIndex( line_source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes( []).setDestinationCrs(poly_source.sourceCrs(), context.transformContext())), feedback) distArea = QgsDistanceArea() distArea.setSourceCrs(poly_source.sourceCrs(), context.transformContext()) distArea.setEllipsoid(context.project().ellipsoid()) features = poly_source.getFeatures() total = 100.0 / poly_source.featureCount() if poly_source.featureCount( ) else 0 for current, poly_feature in enumerate(features): if feedback.isCanceled(): break output_feature = QgsFeature() count = 0 length = 0 if poly_feature.hasGeometry(): poly_geom = poly_feature.geometry() has_intersections = False lines = spatialIndex.intersects(poly_geom.boundingBox()) engine = None if len(lines) > 0: has_intersections = True # use prepared geometries for faster intersection tests engine = QgsGeometry.createGeometryEngine( poly_geom.constGet()) engine.prepareGeometry() if has_intersections: request = QgsFeatureRequest().setFilterFids( lines).setSubsetOfAttributes([]).setDestinationCrs( poly_source.sourceCrs(), context.transformContext()) for line_feature in line_source.getFeatures(request): if feedback.isCanceled(): break if engine.intersects( line_feature.geometry().constGet()): outGeom = poly_geom.intersection( line_feature.geometry()) length += distArea.measureLength(outGeom) count += 1 output_feature.setGeometry(poly_geom) attrs = poly_feature.attributes() if length_field_index == len(attrs): attrs.append(length) else: attrs[length_field_index] = length if count_field_index == len(attrs): attrs.append(count) else: attrs[count_field_index] = count output_feature.setAttributes(attrs) sink.addFeature(output_feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
def getConsoleCommands(self, parameters, context, feedback, executing=True): out = self.parameterAsOutputLayer(parameters, self.OUTPUT, context) extra = self.parameterAsString(parameters, self.EXTRA, context) # if extra is not None: # extra = str(extra) #debug = self.getParameterValue(parameters, self.DEBUG) formula = self.parameterAsString(parameters, self.FORMULA, context) if self.NO_DATA in parameters and parameters[self.NO_DATA] is not None: noData = self.parameterAsDouble(parameters, self.NO_DATA, context) else: noData = None arguments = [] arguments.append('--calc "{}"'.format(formula)) arguments.append('--format') arguments.append(GdalUtils.getFormatShortNameFromFilename(out)) arguments.append('--type') arguments.append(self.TYPE[self.parameterAsEnum( parameters, self.RTYPE, context)]) if noData is not None: arguments.append('--NoDataValue') arguments.append(noData) if extra and len(extra) > 0: arguments.append(extra) #if debug: # arguments.append('--debug') layer = self.parameterAsRasterLayer(parameters, self.INPUT_A, context) if layer is None: raise QgsProcessingException( self.invalidRasterError(parameters, self.INPUT_A)) arguments.append('-A') arguments.append(layer.source()) if self.parameterAsString(parameters, self.BAND_A, context): arguments.append( '--A_band ' + self.parameterAsString(parameters, self.BAND_A, context)) if self.INPUT_B in parameters and parameters[self.INPUT_B] is not None: layer = self.parameterAsRasterLayer(parameters, self.INPUT_B, context) if layer is None: raise QgsProcessingException( self.invalidRasterError(parameters, self.INPUT_B)) arguments.append('-B') arguments.append(layer.source()) if self.parameterAsString(parameters, self.BAND_B, context): arguments.append( '--B_band ' + self.parameterAsString(parameters, self.BAND_B, context)) if self.INPUT_C in parameters and parameters[self.INPUT_C] is not None: layer = self.parameterAsRasterLayer(parameters, self.INPUT_C, context) if layer is None: raise QgsProcessingException( self.invalidRasterError(parameters, self.INPUT_C)) arguments.append('-C') arguments.append(layer.source()) if self.parameterAsString(parameters, self.BAND_C, context): arguments.append( '--C_band ' + self.parameterAsString(parameters, self.BAND_C, context)) if self.INPUT_D in parameters and parameters[self.INPUT_D] is not None: layer = self.parameterAsRasterLayer(parameters, self.INPUT_D, context) if layer is None: raise QgsProcessingException( self.invalidRasterError(parameters, self.INPUT_D)) arguments.append('-D') arguments.append(layer.source()) if self.parameterAsString(parameters, self.BAND_D, context): arguments.append( '--D_band ' + self.parameterAsString(parameters, self.BAND_D, context)) if self.INPUT_E in parameters and parameters[self.INPUT_E] is not None: layer = self.parameterAsRasterLayer(parameters, self.INPUT_E, context) if layer is None: raise QgsProcessingException( self.invalidRasterError(parameters, self.INPUT_E)) arguments.append('-E') arguments.append(layer.source()) if self.parameterAsString(parameters, self.BAND_E, context): arguments.append( '--E_band ' + self.parameterAsString(parameters, self.BAND_E, context)) if self.INPUT_F in parameters and parameters[self.INPUT_F] is not None: layer = self.parameterAsRasterLayer(parameters, self.INPUT_F, context) if layer is None: raise QgsProcessingException( self.invalidRasterError(parameters, self.INPUT_F)) arguments.append('-F') arguments.append(layer.source()) if self.parameterAsString(parameters, self.BAND_F, context): arguments.append( '--F_band ' + self.parameterAsString(parameters, self.BAND_F, context)) options = self.parameterAsString(parameters, self.OPTIONS, context) if options: arguments.extend(GdalUtils.parseCreationOptions(options)) arguments.append('--outfile') arguments.append(out) return [self.commandName(), GdalUtils.escapeAndJoin(arguments)]
def processAlgorithm(self, parameters, context, feedback): commands = list() self.exportedLayers = {} self.preProcessInputs() extent = None crs = None # 1: Export rasters to sgrd and vectors to shp # Tables must be in dbf format. We check that. for param in self.parameterDefinitions(): if isinstance(param, QgsProcessingParameterRasterLayer): if param.name( ) not in parameters or parameters[param.name()] is None: continue if isinstance(parameters[param.name()], str): if parameters[param.name()].lower().endswith('sdat'): self.exportedLayers[param.name( )] = parameters[param.name()][:-4] + 'sgrd' if parameters[param.name()].lower().endswith('sgrd'): self.exportedLayers[param.name()] = parameters[ param.name()] else: layer = self.parameterAsRasterLayer( parameters, param.name(), context) exportCommand = self.exportRasterLayer( param.name(), layer) if exportCommand is not None: commands.append(exportCommand) else: if parameters[param.name()].source().lower().endswith( 'sdat'): self.exportedLayers[param.name( )] = parameters[param.name()].source()[:-4] + 'sgrd' if parameters[param.name()].source().lower().endswith( 'sgrd'): self.exportedLayers[param.name()] = parameters[ param.name()].source() else: exportCommand = self.exportRasterLayer( param.name(), parameters[param.name()]) if exportCommand is not None: commands.append(exportCommand) elif isinstance(param, QgsProcessingParameterFeatureSource): if param.name( ) not in parameters or parameters[param.name()] is None: continue if not crs: source = self.parameterAsSource(parameters, param.name(), context) if source is None: raise QgsProcessingException( self.invalidSourceError(parameters, param.name())) crs = source.sourceCrs() layer_path = self.parameterAsCompatibleSourceLayerPath( parameters, param.name(), context, ['shp'], 'shp', feedback=feedback) if layer_path: self.exportedLayers[param.name()] = layer_path else: raise QgsProcessingException( self.tr('Unsupported file format')) elif isinstance(param, QgsProcessingParameterMultipleLayers): if param.name( ) not in parameters or parameters[param.name()] is None: continue layers = self.parameterAsLayerList(parameters, param.name(), context) if layers is None or len(layers) == 0: continue if param.layerType() == QgsProcessing.TypeRaster: files = [] for i, layer in enumerate(layers): if layer.source().lower().endswith('sdat'): files.append( parameters[param.name()].source()[:-4] + 'sgrd') if layer.source().lower().endswith('sgrd'): files.append(parameters[param.name()].source()) else: exportCommand = self.exportRasterLayer( param.name(), layer) files.append(self.exportedLayers[param.name()]) if exportCommand is not None: commands.append(exportCommand) self.exportedLayers[param.name()] = files else: for layer in layers: temp_params = {} temp_params[param.name()] = layer if not crs: source = self.parameterAsSource( temp_params, param.name(), context) if source is None: raise QgsProcessingException( self.invalidSourceError( parameters, param.name())) crs = source.sourceCrs() layer_path = self.parameterAsCompatibleSourceLayerPath( temp_params, param.name(), context, ['shp'], 'shp', feedback=feedback) if layer_path: if param.name() in self.exportedLayers: self.exportedLayers[param.name()].append( layer_path) else: self.exportedLayers[param.name()] = [ layer_path ] else: raise QgsProcessingException( self.tr('Unsupported file format')) # 2: Set parameters and outputs command = self.undecorated_group + ' "' + self.cmdname + '"' command += ' ' + ' '.join(self.hardcoded_strings) for param in self.parameterDefinitions(): if not param.name() in parameters or parameters[ param.name()] is None: continue if param.isDestination(): continue if isinstance(param, (QgsProcessingParameterRasterLayer, QgsProcessingParameterFeatureSource)): command += ' -{} "{}"'.format( param.name(), self.exportedLayers[param.name()]) elif isinstance(param, QgsProcessingParameterMultipleLayers): if parameters[ param.name()]: # parameter may have been an empty list command += ' -{} "{}"'.format( param.name(), ';'.join(self.exportedLayers[param.name()])) elif isinstance(param, QgsProcessingParameterBoolean): if self.parameterAsBool(parameters, param.name(), context): command += ' -{} true'.format(param.name().strip()) else: command += ' -{} false'.format(param.name().strip()) elif isinstance(param, QgsProcessingParameterMatrix): tempTableFile = getTempFilename('txt') with open(tempTableFile, 'w') as f: f.write('\t'.join([col for col in param.headers()]) + '\n') values = self.parameterAsMatrix(parameters, param.name(), context) for i in range(0, len(values), 3): s = '{}\t{}\t{}\n'.format(values[i], values[i + 1], values[i + 2]) f.write(s) command += ' -{} "{}"'.format(param.name(), tempTableFile) elif isinstance(param, QgsProcessingParameterExtent): # 'We have to substract/add half cell size, since SAGA is # center based, not corner based halfcell = self.getOutputCellsize(parameters, context) / 2 offset = [halfcell, -halfcell, halfcell, -halfcell] rect = self.parameterAsExtent(parameters, param.name(), context) values = [] values.append(rect.xMinimum()) values.append(rect.xMaximum()) values.append(rect.yMinimum()) values.append(rect.yMaximum()) for i in range(4): command += ' -{} {}'.format(param.name().split(' ')[i], float(values[i]) + offset[i]) elif isinstance(param, QgsProcessingParameterNumber): if param.dataType() == QgsProcessingParameterNumber.Integer: command += ' -{} {}'.format( param.name(), self.parameterAsInt(parameters, param.name(), context)) else: command += ' -{} {}'.format( param.name(), self.parameterAsDouble(parameters, param.name(), context)) elif isinstance(param, QgsProcessingParameterEnum): command += ' -{} {}'.format( param.name(), self.parameterAsEnum(parameters, param.name(), context)) elif isinstance( param, (QgsProcessingParameterString, QgsProcessingParameterFile)): command += ' -{} "{}"'.format( param.name(), self.parameterAsFile(parameters, param.name(), context)) elif isinstance( param, (QgsProcessingParameterString, QgsProcessingParameterField)): command += ' -{} "{}"'.format( param.name(), self.parameterAsString(parameters, param.name(), context)) output_layers = [] output_files = {} for out in self.destinationParameterDefinitions(): filePath = self.parameterAsOutputLayer(parameters, out.name(), context) if isinstance(out, (QgsProcessingParameterRasterDestination, QgsProcessingParameterVectorDestination)): output_layers.append(filePath) output_files[out.name()] = filePath command += ' -{} "{}"'.format(out.name(), filePath) commands.append(command) # special treatment for RGB algorithm # TODO: improve this and put this code somewhere else for out in self.destinationParameterDefinitions(): if isinstance(out, QgsProcessingParameterRasterDestination): filename = self.parameterAsOutputLayer(parameters, out.name(), context) filename2 = os.path.splitext(filename)[0] + '.sgrd' if self.cmdname == 'RGB Composite': commands.append( 'io_grid_image 0 -IS_RGB -GRID:"{}" -FILE:"{}"'.format( filename2, filename)) # 3: Run SAGA commands = self.editCommands(commands) SagaUtils.createSagaBatchJobFileFromSagaCommands(commands) loglines = [] loglines.append(self.tr('SAGA execution commands')) for line in commands: feedback.pushCommandInfo(line) loglines.append(line) if ProcessingConfig.getSetting(SagaUtils.SAGA_LOG_COMMANDS): QgsMessageLog.logMessage('\n'.join(loglines), self.tr('Processing'), Qgis.Info) SagaUtils.executeSaga(feedback) if crs is not None: for out in output_layers: prjFile = os.path.splitext(out)[0] + '.prj' with open(prjFile, 'w') as f: f.write(crs.toWkt()) result = {} for o in self.outputDefinitions(): if o.name() in output_files: result[o.name()] = output_files[o.name()] return result
def processAlgorithm(self, parameters, context, feedback): layer = self.parameterAsSource(parameters, ConcaveHull.INPUT, context) if layer is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT)) alpha = self.parameterAsDouble(parameters, self.ALPHA, context) holes = self.parameterAsBool(parameters, self.HOLES, context) no_multigeom = self.parameterAsBool(parameters, self.NO_MULTIGEOMETRY, context) # Delaunay triangulation from input point layer feedback.setProgressText( QCoreApplication.translate('ConcaveHull', 'Creating Delaunay triangles…')) delaunay_layer = processing.run("qgis:delaunaytriangulation", { 'INPUT': parameters[ConcaveHull.INPUT], 'OUTPUT': 'memory:' }, feedback=feedback, context=context)['OUTPUT'] # Get max edge length from Delaunay triangles feedback.setProgressText( QCoreApplication.translate('ConcaveHull', 'Computing edges max length…')) features = delaunay_layer.getFeatures() count = delaunay_layer.featureCount() if count == 0: raise QgsProcessingException( self.tr('No Delaunay triangles created.')) counter = 50. / count lengths = [] edges = {} for feat in features: if feedback.isCanceled(): break line = feat.geometry().asPolygon()[0] for i in range(len(line) - 1): lengths.append(sqrt(line[i].sqrDist(line[i + 1]))) edges[feat.id()] = max(lengths[-3:]) feedback.setProgress(feat.id() * counter) max_length = max(lengths) # Get features with longest edge longer than alpha*max_length feedback.setProgressText( QCoreApplication.translate('ConcaveHull', 'Removing features…')) counter = 50. / len(edges) i = 0 ids = [] for id, max_len in list(edges.items()): if feedback.isCanceled(): break if max_len > alpha * max_length: ids.append(id) feedback.setProgress(50 + i * counter) i += 1 # Remove features delaunay_layer.dataProvider().deleteFeatures(ids) # Dissolve all Delaunay triangles feedback.setProgressText( QCoreApplication.translate('ConcaveHull', 'Dissolving Delaunay triangles…')) dissolved_layer = processing.run("native:dissolve", { 'INPUT': delaunay_layer, 'OUTPUT': 'memory:' }, feedback=feedback, context=context)['OUTPUT'] # Save result feedback.setProgressText( QCoreApplication.translate('ConcaveHull', 'Saving data…')) feat = QgsFeature() dissolved_layer.getFeatures().nextFeature(feat) # Not needed anymore, free up some resources del delaunay_layer del dissolved_layer (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, layer.fields(), QgsWkbTypes.Polygon, layer.sourceCrs()) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) geom = feat.geometry() if no_multigeom and geom.isMultipart(): # Only singlepart geometries are allowed geom_list = geom.asGeometryCollection() for single_geom in geom_list: if feedback.isCanceled(): break single_feature = QgsFeature() if not holes: # Delete holes single_geom = single_geom.removeInteriorRings() single_feature.setGeometry(single_geom) sink.addFeature(single_feature, QgsFeatureSink.FastInsert) else: # Multipart geometries are allowed if not holes: # Delete holes geom = geom.removeInteriorRings() feat.setGeometry(geom) sink.addFeature(feat, QgsFeatureSink.FastInsert) return {self.OUTPUT: dest_id}
def processAlgorithm(self, parameters, context, feedback): if parameters[self.INPUT] == parameters[self.HUBS]: raise QgsProcessingException( self.tr('Same layer given for both hubs and spokes')) point_source = self.parameterAsSource(parameters, self.INPUT, context) if point_source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT)) hub_source = self.parameterAsSource(parameters, self.HUBS, context) if hub_source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.HUBS)) fieldName = self.parameterAsString(parameters, self.FIELD, context) units = self.UNITS[self.parameterAsEnum(parameters, self.UNIT, context)] fields = point_source.fields() fields.append(QgsField('HubName', QVariant.String)) fields.append(QgsField('HubDist', QVariant.Double)) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, QgsWkbTypes.LineString, point_source.sourceCrs()) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) index = QgsSpatialIndex( hub_source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes( []).setDestinationCrs(point_source.sourceCrs(), context.transformContext()))) distance = QgsDistanceArea() distance.setSourceCrs(point_source.sourceCrs(), context.transformContext()) distance.setEllipsoid(context.project().ellipsoid()) # Scan source points, find nearest hub, and write to output file features = point_source.getFeatures() total = 100.0 / point_source.featureCount( ) if point_source.featureCount() else 0 for current, f in enumerate(features): if feedback.isCanceled(): break if not f.hasGeometry(): sink.addFeature(f, QgsFeatureSink.FastInsert) continue src = f.geometry().boundingBox().center() neighbors = index.nearestNeighbor(src, 1) ft = next( hub_source.getFeatures(QgsFeatureRequest().setFilterFid( neighbors[0]).setSubsetOfAttributes( [fieldName], hub_source.fields()).setDestinationCrs( point_source.sourceCrs(), context.transformContext()))) closest = ft.geometry().boundingBox().center() hubDist = distance.measureLine(src, closest) if units != self.LAYER_UNITS: hub_dist_in_desired_units = distance.convertLengthMeasurement( hubDist, units) else: hub_dist_in_desired_units = hubDist attributes = f.attributes() attributes.append(ft[fieldName]) attributes.append(hub_dist_in_desired_units) feat = QgsFeature() feat.setAttributes(attributes) feat.setGeometry(QgsGeometry.fromPolylineXY([src, closest])) sink.addFeature(feat, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
def processAlgorithm(self, parameters, context, feedback): poly_source = self.parameterAsSource(parameters, self.POLYGONS, context) if poly_source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.POLYGONS)) point_source = self.parameterAsSource(parameters, self.POINTS, context) if point_source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.POINTS)) weight_field = self.parameterAsString(parameters, self.WEIGHT, context) weight_field_index = -1 if weight_field: weight_field_index = point_source.fields().lookupField( weight_field) class_field = self.parameterAsString(parameters, self.CLASSFIELD, context) class_field_index = -1 if class_field: class_field_index = point_source.fields().lookupField(class_field) field_name = self.parameterAsString(parameters, self.FIELD, context) fields = poly_source.fields() if fields.lookupField(field_name) < 0: fields.append(QgsField(field_name, QVariant.Int)) field_index = fields.lookupField(field_name) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, poly_source.wkbType(), poly_source.sourceCrs()) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) spatialIndex = QgsSpatialIndex( point_source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes( []).setDestinationCrs(poly_source.sourceCrs(), context.transformContext())), feedback) point_attribute_indices = [] if weight_field_index >= 0: point_attribute_indices.append(weight_field_index) if class_field_index >= 0: point_attribute_indices.append(class_field_index) features = poly_source.getFeatures() total = 100.0 / poly_source.featureCount() if poly_source.featureCount( ) else 0 for current, polygon_feature in enumerate(features): if feedback.isCanceled(): break count = 0 output_feature = QgsFeature() if polygon_feature.hasGeometry(): geom = polygon_feature.geometry() engine = QgsGeometry.createGeometryEngine(geom.constGet()) engine.prepareGeometry() count = 0 classes = set() points = spatialIndex.intersects(geom.boundingBox()) if len(points) > 0: request = QgsFeatureRequest().setFilterFids( points).setDestinationCrs(poly_source.sourceCrs(), context.transformContext()) request.setSubsetOfAttributes(point_attribute_indices) for point_feature in point_source.getFeatures(request): if feedback.isCanceled(): break if engine.contains( point_feature.geometry().constGet()): if weight_field_index >= 0: weight = point_feature.attributes( )[weight_field_index] try: count += float(weight) except: # Ignore fields with non-numeric values pass elif class_field_index >= 0: point_class = point_feature.attributes( )[class_field_index] if point_class not in classes: classes.add(point_class) else: count += 1 output_feature.setGeometry(geom) attrs = polygon_feature.attributes() if class_field_index >= 0: score = len(classes) else: score = count if field_index == len(attrs): attrs.append(score) else: attrs[field_index] = score output_feature.setAttributes(attrs) sink.addFeature(output_feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}