def _geometry_num_vertices(geometry: QgsGeometry): if geometry.isNull() or geometry.isEmpty(): return 0 index = 0 for v in geometry.vertices(): index += 1 return index
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().fromPolygon([polygon]) feat.setGeometry(geometry) sink.addFeature(feat, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
def processAlgorithm(self, feedback): layer = dataobjects.getObjectFromUri(self.getParameterValue( self.INPUT)) fields = [ QgsField('POINTA', QVariant.Double, '', 24, 15), QgsField('POINTB', QVariant.Double, '', 24, 15), QgsField('POINTC', QVariant.Double, '', 24, 15) ] writer = self.getOutputFromName(self.OUTPUT).getVectorWriter( fields, QgsWkbTypes.Polygon, layer.crs()) pts = [] ptDict = {} ptNdx = -1 c = voronoi.Context() features = vector.features(layer) total = 100.0 / len(features) for current, inFeat in enumerate(features): 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 GeoAlgorithmExecutionException( 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) for current, triangle in enumerate(triangles): 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(layer.getFeatures(request)) geom = QgsGeometry(inFeat.geometry()) if geom.isMultipart(): point = QgsPoint(geom.asMultiPoint()[n]) else: point = QgsPoint(geom.asPoint()) polygon.append(point) if step <= 3: attrs.append(ids[index]) step += 1 feat.setAttributes(attrs) geometry = QgsGeometry().fromPolygon([polygon]) feat.setGeometry(geometry) writer.addFeature(feat) feedback.setProgress(int(current * total)) del writer
def features(self, request=None, clipGeom=None): settings = self.writer.settings mapTo3d = settings.mapTo3d() baseExtent = settings.baseExtent baseExtentGeom = baseExtent.geometry() rotation = baseExtent.rotation() prop = self.prop useZ = prop.useZ() if useZ: srs_from = osr.SpatialReference() srs_from.ImportFromProj4(str(self.layer.crs().toProj4())) srs_to = osr.SpatialReference() srs_to.ImportFromProj4(str(self.writer.settings.crs.toProj4())) ogr_transform = osr.CreateCoordinateTransformation(srs_from, srs_to) clipGeomWkb = bytes(clipGeom.exportToWkb()) if clipGeom else None ogr_clipGeom = ogr.CreateGeometryFromWkb(clipGeomWkb) if clipGeomWkb else None else: # z_func: function to get elevation at given point (x, y) on surface if prop.isHeightRelativeToDEM(): if self.geomType == QgsWkbTypes.PolygonGeometry and prop.type_index == 1: # Overlay z_func = lambda x, y: 0 else: # get elevation from DEM z_func = lambda x, y: self.writer.demProvider.readValue(x, y) else: z_func = lambda x, y: 0 feats = [] request = request or QgsFeatureRequest() for f in self.layer.getFeatures(request): geometry = f.geometry() if geometry is None: logMessage("null geometry skipped") continue # coordinate transformation - layer crs to project crs geom = QgsGeometry(geometry) if geom.transform(self.transform) != 0: logMessage("Failed to transform geometry") continue # check if geometry intersects with the base extent (rotated rect) if rotation and not baseExtentGeom.intersects(geom): continue # create feature feat = Feature(self.writer, self, f) # transform_func: function to transform the map coordinates to 3d coordinates relativeHeight = prop.relativeHeight(f) def transform_func(x, y, z): return mapTo3d.transform(x, y, z + relativeHeight) if useZ: ogr_geom = ogr.CreateGeometryFromWkb(bytes(geometry.exportToWkb())) # transform geometry from layer CRS to project CRS if ogr_geom.Transform(ogr_transform) != 0: logMessage("Failed to transform geometry") continue # clip geometry if ogr_clipGeom and self.geomType == QgsWkbTypes.LineGeometry: ogr_geom = ogr_geom.Intersection(ogr_clipGeom) if ogr_geom is None: continue # check if geometry is empty if ogr_geom.IsEmpty(): logMessage("empty geometry skipped") continue feat.geom = self.geomClass.fromOgrGeometry25D(ogr_geom, transform_func) else: # clip geometry if clipGeom and self.geomType in [QgsWkbTypes.LineGeometry, QgsWkbTypes.PolygonGeometry]: geom = geom.intersection(clipGeom) if geom is None: continue # skip if geometry is empty or null if geom.isEmpty() or geom.isNull(): logMessage("empty/null geometry skipped") continue if self.geomType == QgsWkbTypes.PolygonGeometry: feat.geom = self.geomClass.fromQgsGeometry(geom, z_func, transform_func, self.hasLabel()) if prop.type_index == 1 and prop.isHeightRelativeToDEM(): # Overlay and relative to DEM feat.geom.splitPolygon(self.writer.triangleMesh()) else: feat.geom = self.geomClass.fromQgsGeometry(geom, z_func, transform_func) if feat.geom is None: continue #yield feat feats.append(feat) return feats
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) source_fields_parameter = self.parameterAsFields( parameters, self.INPUT_FIELD, context) target = self.parameterAsVectorLayer(parameters, self.OUTPUT, context) target_fields_parameter = self.parameterAsFields( parameters, self.OUTPUT_FIELD, context) action_on_duplicate = self.parameterAsEnum(parameters, self.ACTION_ON_DUPLICATE, context) results = { self.OUTPUT: None, self.APPENDED_COUNT: None, self.UPDATED_COUNT: None, self.SKIPPED_COUNT: None } target_value_dict = dict() source_field_unique_values = '' target_field_unique_values = '' source_field_type = None target_field_type = None if source_fields_parameter: source_field_unique_values = source_fields_parameter[0] source_field_type = source.fields().field( source_field_unique_values).type() if target_fields_parameter: target_field_unique_values = target_fields_parameter[0] target_field_type = target.fields().field( target_field_unique_values).type() if source_field_type != target_field_type: feedback.pushInfo( "\nWARNING: Source and target fields to compare have different field types." ) if source_field_unique_values and target_field_unique_values and action_on_duplicate == self.NO_ACTION: feedback.reportError( "\nWARNING: Since you have chosen source and target fields to compare, you need to choose a valid action to apply on duplicate features before running this algorithm." ) return results if action_on_duplicate != self.NO_ACTION and not ( source_field_unique_values and target_field_unique_values): feedback.reportError( "\nWARNING: Since you have chosen an action on duplicate features, you need to choose both source and target fields for comparing values before running this algorithm." ) return results caps = target.dataProvider().capabilities() if not (caps & QgsVectorDataProvider.AddFeatures): feedback.reportError( "\nWARNING: The target layer does not support appending features to it! Choose another target layer." ) return results if action_on_duplicate == self.UPDATE_EXISTING_FEATURE and not ( caps & QgsVectorDataProvider.ChangeAttributeValues and caps & QgsVectorDataProvider.ChangeGeometries): feedback.reportError( "\nWARNING: The target layer does not support updating its features! Choose another action for duplicate features or choose another target layer." ) return results editable_before = False if target.isEditable(): editable_before = True feedback.reportError( "\nWARNING: You need to close the edit session on layer '{}' before running this algorithm." .format(target.name())) return results # Define a mapping between source and target layer mapping = dict() for target_idx in target.fields().allAttributesList(): target_field = target.fields().field(target_idx) source_idx = source.fields().indexOf(target_field.name()) if source_idx != -1: mapping[target_idx] = source_idx # Build dict of target field values so that we can search easily later {value1: [id1, id2], ...} if target_field_unique_values: for f in target.getFeatures(): if f[target_field_unique_values] in target_value_dict: target_value_dict[f[target_field_unique_values]].append( int(f.id())) else: target_value_dict[f[target_field_unique_values]] = [ int(f.id()) ] # Prepare features for the Copy and Paste results[self.APPENDED_COUNT] = 0 total = 100.0 / source.featureCount() if source.featureCount() else 0 features = source.getFeatures() destType = target.geometryType() destIsMulti = QgsWkbTypes.isMultiType(target.wkbType()) new_features = list() updated_features = dict() updated_geometries = dict() updated_features_count = 0 updated_geometries_count = 0 skipped_features_count = 0 # To properly count features that were skipped duplicate_features_set = set( ) # To properly count features that were updated for current, in_feature in enumerate(features): if feedback.isCanceled(): break target_feature_exists = False duplicate_target_value = None # If skip is the action, skip as soon as possible if source_field_unique_values: duplicate_target, duplicate_target_value = self.find_duplicate_value( in_feature[source_field_unique_values], source_field_type, target_value_dict, target_field_type) if duplicate_target: if action_on_duplicate == self.SKIP_FEATURE: request = QgsFeatureRequest( target_value_dict[duplicate_target_value] ) # Get target feature ids request.setFlags(QgsFeatureRequest.NoGeometry) request.setSubsetOfAttributes( []) # Note: this adds a new flag skipped_features_count += len( [f for f in target.getFeatures(request)]) continue target_feature_exists = True attrs = { target_idx: in_feature[source_idx] for target_idx, source_idx in mapping.items() } geom = QgsGeometry() if in_feature.hasGeometry() and target.isSpatial(): # Convert geometry to match destination layer # Adapted from QGIS qgisapp.cpp, pasteFromClipboard() geom = in_feature.geometry() if not geom.isNull(): if destType != QgsWkbTypes.UnknownGeometry: newGeometry = geom.convertToType(destType, destIsMulti) if newGeometry.isNull(): continue # Couldn't convert geom = newGeometry # Avoid intersection if enabled in digitize settings geom.avoidIntersections( QgsProject.instance().avoidIntersectionsLayers()) if target_feature_exists and action_on_duplicate == self.UPDATE_EXISTING_FEATURE: for t_f in target.getFeatures( target_value_dict[duplicate_target_value]): duplicate_features_set.add(t_f.id()) updated_features[t_f.id()] = attrs if target.isSpatial(): updated_geometries[t_f.id()] = geom else: # Append new_feature = QgsVectorLayerUtils().createFeature( target, geom, attrs) new_features.append(new_feature) feedback.setProgress(int(current * total)) # Do the Copy and Paste res_add_features = False try: with edit(target): target.beginEditCommand("Appending/Updating features...") if updated_features: for k, v in updated_features.items(): if target.changeAttributeValues(k, v): updated_features_count += 1 else: feedback.reportError( "\nERROR: Target feature (id={}) couldn't be updated to the following attributes: {}." .format(k, v)) if updated_geometries: for k, v in updated_geometries.items(): if target.changeGeometry(k, v): updated_geometries_count += 1 else: feedback.reportError( "\nERROR: Target feature's geometry (id={}) couldn't be updated." .format(k)) if new_features: res_add_features = target.addFeatures(new_features) target.endEditCommand() except QgsEditError as e: if not editable_before: # Let's close the edit session to prepare for a next run target.rollBack() feedback.reportError( "\nERROR: No features could be appended/updated to/in '{}', because of the following error:\n{}\n" .format(target.name(), repr(e))) return results if action_on_duplicate == self.SKIP_FEATURE: feedback.pushInfo( "\nSKIPPED FEATURES: {} duplicate features were skipped while copying features to '{}'!" .format(skipped_features_count, target.name())) results[self.SKIPPED_COUNT] = skipped_features_count if action_on_duplicate == self.UPDATE_EXISTING_FEATURE: feedback.pushInfo( "\nUPDATED FEATURES: {} out of {} duplicate features were updated while copying features to '{}'!" .format(updated_features_count, len(duplicate_features_set), target.name())) results[self.UPDATED_COUNT] = updated_features_count if not new_features: feedback.pushInfo( "\nFINISHED WITHOUT APPENDED FEATURES: There were no features to append to '{}'." .format(target.name())) else: if res_add_features: feedback.pushInfo( "\nAPPENDED FEATURES: {} out of {} features from input layer were successfully appended to '{}'!" .format(len(new_features), source.featureCount(), target.name())) results[self.APPENDED_COUNT] = len(new_features) else: # TODO do we really need this else message below? feedback.reportError( "\nERROR: The {} features from input layer could not be appended to '{}'. Sometimes this might be due to NOT NULL constraints that are not met." .format(source.featureCount(), target.name())) results[self.OUTPUT] = target return results
def processAlgorithm(self, parameters, context, feedback): layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(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)) writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(fields, QgsWkbTypes.Polygon, layer.crs(), context) pts = [] ptDict = {} ptNdx = -1 c = voronoi.Context() features = QgsProcessingUtils.getFeatures(layer, context) total = 100.0 / layer.featureCount() if layer.featureCount() else 0 for current, inFeat in enumerate(features): 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 GeoAlgorithmExecutionException( 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): 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(layer.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().fromPolygon([polygon]) feat.setGeometry(geometry) writer.addFeature(feat, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) del writer
def processing(options, f, progressBar, progressMessage): ''' Select trees which are on the contour of the forest and isolated trees. ''' # Export Grid contour and isolated to crowns values forestSelectedPath = options['dst'] + 'tif/' + f + \ '_forest_selected.tif' crownsPath = options['dst'] + 'shp/' + f + '_crowns.shp' # crownsStatsPath = options['dst'] + 'shp/' + f + '_crowns_stats.shp' outputDir = options["dst"] fileTxt = open(outputDir + "/log.txt", "a") fileTxt.write("gridstatisticsforpolygons started\n") fileTxt.close() crowns = QgsVectorLayer(crownsPath, "crowns", "ogr") inputStatRaster = QgsRasterLayer(forestSelectedPath, "forestSelected") z_stat = QgsZonalStatistics(crowns, inputStatRaster, '_', 1, QgsZonalStatistics.Max) result_z_stat = z_stat.calculateStatistics(QgsFeedback()) outputDir = options["dst"] fileTxt = open(outputDir + "/log.txt", "a") fileTxt.write("gridstatisticsforpolygons passed\n") fileTxt.close() # crowns = QgsVectorLayer(crownsStatsPath, 'Crowns stats', 'ogr') crowns.selectByExpression('"_max"=1.0') selected_array = crowns.getValues("N", True) crowns.invertSelection() unselected_array = crowns.getValues("N", True) unselected_crowns_ids = crowns.getValues("$id", True) unselected_top_ids = crowns.getValues('"N" - 1', True) crowns.dataProvider().deleteFeatures(unselected_crowns_ids[0]) treetopsPath = options['dst'] + 'shp/' + f + '_treetops.shp' treetops = QgsVectorLayer(treetopsPath, 'Tree tops', 'ogr') treetops.dataProvider().deleteFeatures(unselected_top_ids[0]) treetopsSelectedPath = options['dst'] + 'shp/' + f + \ '_treetops_selected.shp' crownsSelectedPath = options['dst'] + 'shp/' + f + '_crowns_selected.shp' treetopsTrianglesPath = options['dst'] + 'shp/' + f + \ '_treetops_triangles.shp' outputDir = options["dst"] fileTxt = open(outputDir + "/log.txt", "a") fileTxt.write("advancedpythonfieldcalculator started\n") fileTxt.close() treetops.dataProvider().addAttributes([QgsField('N', QVariant.Int)]) treetops.updateFields() treetops.startEditing() for treetop in treetops.getFeatures(): treetops.changeAttributeValue(treetop.id(), treetop.fieldNameIndex('N'), treetop.id()) treetops.commitChanges() outputDir = options["dst"] fileTxt = open(outputDir + "/log.txt", "a") fileTxt.write("joinattributesbylocation started\n") fileTxt.close() # Adapted from https://github.com/qgis/QGIS-Processing # TODO: replace by native QGIS c++ algo when available... crowns.dataProvider().addAttributes([QgsField('tid', QVariant.Int)]) crowns.updateFields() crowns.startEditing() fcount = crowns.featureCount() counter = 0 for crown in crowns.getFeatures(): counter += 1 progressBar.setValue(100 + int(counter * (600 / fcount))) progressMessage.setText('Joining crown ' + str(counter) + '/' + str(fcount)) request = QgsFeatureRequest() request.setFilterRect(crown.geometry().boundingBox()) dp = treetops.dataProvider() for r in dp.getFeatures(request): if crown.geometry().intersects(r.geometry()): crowns.changeAttributeValue(crown.id(), crown.fieldNameIndex('tid'), r.id()) crowns.commitChanges() fileTxt = open(outputDir + "/log.txt", "a") fileTxt.write("delaunaytriangulation started\n") fileTxt.close() # delaunay triangulation Adapted from official Python plugin # TODO: replace by native QGIS c++ algo when available... 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)) crs = QgsCoordinateReferenceSystem('EPSG:2056') triangleFile = QgsVectorFileWriter(treetopsTrianglesPath, 'utf-8', fields, QgsWkbTypes.Polygon, crs, 'ESRI Shapefile') pts = [] ptDict = {} ptNdx = -1 c = voronoi.Context() features = treetops.getFeatures() total = 100.0 / treetops.featureCount() if treetops.featureCount() else 0 progressMessage.setText('Starting triangulation...') for current, inFeat in enumerate(features): 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) progressMessage.setText('Triangulation step 1 ok') if len(pts) < 3: raise QgsProcessingException( '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): 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(treetops.getFeatures(request)) geom = QgsGeometry(inFeat.geometry()) point = QgsPoint(geom.asPoint()) polygon.append(point) if step <= 3: attrs.append(ids[index]) step += 1 linestring = QgsLineString(polygon) poly = QgsPolygon() poly.setExteriorRing(linestring) feat.setAttributes(attrs) geometry = QgsGeometry().fromWkt(poly.asWkt()) feat.setGeometry(geometry) triangleFile.addFeature(feat) progressMessage.setText('Triangulation terminated') # Remove triangles with perimeter higher than threshold triangles = QgsVectorLayer(treetopsTrianglesPath, 'triangles', 'ogr') maxPeri = str(options['MaxTrianglePerimeter']) triangles.selectByExpression('$perimeter > ' + maxPeri) triangles_to_delete_ids = triangles.getValues("$id", True) triangles.dataProvider().deleteFeatures(triangles_to_delete_ids[0]) outputDir = options["dst"] fileTxt = open(outputDir + "/log.txt", "a") fileTxt.write("treeSelector passed\n") fileTxt.close() progressMessage.setText('Starting convexhull computing...')