Skip to content
Permalink
Browse files

Port a multi-step algorithm to new API (concave hull)

  • Loading branch information
nyalldawson committed Jul 6, 2017
1 parent a15d283 commit db816ec3fee53ff9d96072405ac8b15b29b1f0fb
@@ -32,14 +32,16 @@
QgsFeatureSink,
QgsWkbTypes,
QgsApplication,
QgsProcessingUtils)
QgsProcessingUtils,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterVectorLayer,
QgsProcessingParameterDefinition,
QgsProcessingParameterNumber,
QgsProcessingParameterBoolean,
QgsProcessingParameterFeatureSink,
QgsProcessingOutputVectorLayer)
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
from processing.core.parameters import ParameterVector
from processing.core.parameters import ParameterNumber
from processing.core.parameters import ParameterBoolean
from processing.core.outputs import OutputVector
from processing.tools import dataobjects
import processing
from math import sqrt

@@ -57,17 +59,19 @@ def group(self):

def __init__(self):
super().__init__()
self.addParameter(ParameterVector(ConcaveHull.INPUT,
self.tr('Input point layer'), [dataobjects.TYPE_VECTOR_POINT]))
self.addParameter(ParameterNumber(self.ALPHA,
self.tr('Threshold (0-1, where 1 is equivalent with Convex Hull)'),
0, 1, 0.3))
self.addParameter(ParameterBoolean(self.HOLES,
self.tr('Allow holes'), True))
self.addParameter(ParameterBoolean(self.NO_MULTIGEOMETRY,
self.tr('Split multipart geometry into singleparts geometries'), False))
self.addOutput(
OutputVector(ConcaveHull.OUTPUT, self.tr('Concave hull'), datatype=[dataobjects.TYPE_VECTOR_POLYGON]))

self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, self.tr('Input point layer'), [QgsProcessingParameterDefinition.TypeVectorPoint]))
self.addParameter(QgsProcessingParameterNumber(self.ALPHA,
self.tr('Threshold (0-1, where 1 is equivalent with Convex Hull)'),
minValue=0, maxValue=1, defaultValue=0.3, type=QgsProcessingParameterNumber.Double))

self.addParameter(QgsProcessingParameterBoolean(self.HOLES,
self.tr('Allow holes'), defaultValue=True))
self.addParameter(QgsProcessingParameterBoolean(self.NO_MULTIGEOMETRY,
self.tr('Split multipart geometry into singleparts geometries'), defaultValue=False))

self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Concave hull'), type=QgsProcessingParameterDefinition.TypeVectorPolygon))
self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr("Concave hull"), type=QgsProcessingParameterDefinition.TypeVectorPolygon))

def name(self):
return 'concavehull'
@@ -76,28 +80,31 @@ def displayName(self):
return self.tr('Concave hull')

def processAlgorithm(self, parameters, context, feedback):
layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(ConcaveHull.INPUT), context)
alpha = self.getParameterValue(self.ALPHA)
holes = self.getParameterValue(self.HOLES)
no_multigeom = self.getParameterValue(self.NO_MULTIGEOMETRY)
layer = self.parameterAsSource(parameters, ConcaveHull.INPUT, context)
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(self.tr('Creating Delaunay triangles...'))
delone_triangles = processing.run("qgis:delaunaytriangulation", layer, None, context=context)['OUTPUT']
delone_triangles = processing.run("qgis:delaunaytriangulation", {'INPUT': parameters[ConcaveHull.INPUT], 'OUTPUT': 'memory:'}, feedback=feedback, context=context)['OUTPUT']
delaunay_layer = QgsProcessingUtils.mapLayerFromString(delone_triangles, context)

# Get max edge length from Delaunay triangles
feedback.setProgressText(self.tr('Computing edges max length...'))

features = QgsProcessingUtils.getFeatures(delaunay_layer, context)
count = QgsProcessingUtils.featureCount(delaunay_layer, context)
features = delaunay_layer.getFeatures()
count = delaunay_layer.featureCount()
if count == 0:
raise GeoAlgorithmExecutionException(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])))
@@ -111,6 +118,9 @@ def processAlgorithm(self, parameters, context, feedback):
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)
@@ -124,21 +134,25 @@ def processAlgorithm(self, parameters, context, feedback):

# Dissolve all Delaunay triangles
feedback.setProgressText(self.tr('Dissolving Delaunay triangles...'))
dissolved = processing.run("qgis:dissolve", delaunay_layer.id(),
True, None, None, context=context)['OUTPUT']
dissolved = processing.run("native:dissolve", {'INPUT': delaunay_layer.id(), 'OUTPUT': 'memory:'}, feedback=feedback, context=context)['OUTPUT']
dissolved_layer = QgsProcessingUtils.mapLayerFromString(dissolved, context)

# Save result
feedback.setProgressText(self.tr('Saving data...'))
feat = QgsFeature()
QgsProcessingUtils.getFeatures(dissolved_layer, context).nextFeature(feat)
writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(layer.fields(), QgsWkbTypes.Polygon,
layer.crs(), context)
dissolved_layer.getFeatures().nextFeature(feat)

(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
layer.fields(), QgsWkbTypes.Polygon, layer.sourceCrs())

geom = feat.geometry()
if no_multigeom and geom.isMultipart():
# Only singlepart geometries are allowed
geom_list = geom.asMultiPolygon()
for single_geom_list in geom_list:
if feedback.isCanceled():
break

single_feature = QgsFeature()
single_geom = QgsGeometry.fromPolygon(single_geom_list)
if not holes:
@@ -147,13 +161,14 @@ def processAlgorithm(self, parameters, context, feedback):
while deleted:
deleted = single_geom.deleteRing(1)
single_feature.setGeometry(single_geom)
writer.addFeature(single_feature, QgsFeatureSink.FastInsert)
sink.addFeature(single_feature, QgsFeatureSink.FastInsert)
else:
# Multipart geometries are allowed
if not holes:
# Delete holes
deleted = True
while deleted:
deleted = geom.deleteRing(1)
writer.addFeature(feat, QgsFeatureSink.FastInsert)
del writer
sink.addFeature(feat, QgsFeatureSink.FastInsert)

return {self.OUTPUT: dest_id}
@@ -47,6 +47,7 @@
from .Boundary import Boundary
from .BoundingBox import BoundingBox
from .CheckValidity import CheckValidity
from .ConcaveHull import ConcaveHull
from .CreateAttributeIndex import CreateAttributeIndex
from .Delaunay import Delaunay
from .DeleteColumn import DeleteColumn
@@ -110,7 +111,6 @@
# from .HubDistanceLines import HubDistanceLines
# from .HubLines import HubLines
# from .GeometryConvert import GeometryConvert
# from .ConcaveHull import ConcaveHull
# from .RasterLayerStatistics import RasterLayerStatistics
# from .StatisticsByCategories import StatisticsByCategories
# from .EquivalentNumField import EquivalentNumField
@@ -206,7 +206,7 @@ def getAlgs(self):
# JoinAttributes(),
# Explode(), FieldsPyculator(),
# EquivalentNumField(),
# StatisticsByCategories(), ConcaveHull(),
# StatisticsByCategories(),
# RasterLayerStatistics(), PointsDisplacement(),
# PointsFromPolygons(),
# PointsFromLines(), RandomPointsExtent(),
@@ -246,6 +246,7 @@ def getAlgs(self):
Boundary(),
BoundingBox(),
CheckValidity(),
ConcaveHull(),
CreateAttributeIndex(),
Delaunay(),
DeleteColumn(),

0 comments on commit db816ec

Please sign in to comment.
You can’t perform that action at this time.