Skip to content

Commit

Permalink
Port a multi-step algorithm to new API (concave hull)
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Jul 6, 2017
1 parent a15d283 commit db816ec
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 34 deletions.
79 changes: 47 additions & 32 deletions python/plugins/processing/algs/qgis/ConcaveHull.py
Expand Up @@ -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

Expand All @@ -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'
Expand All @@ -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])))
Expand All @@ -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)
Expand All @@ -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:
Expand All @@ -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}
5 changes: 3 additions & 2 deletions python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -206,7 +206,7 @@ def getAlgs(self):
# JoinAttributes(),
# Explode(), FieldsPyculator(),
# EquivalentNumField(),
# StatisticsByCategories(), ConcaveHull(),
# StatisticsByCategories(),
# RasterLayerStatistics(), PointsDisplacement(),
# PointsFromPolygons(),
# PointsFromLines(), RandomPointsExtent(),
Expand Down Expand Up @@ -246,6 +246,7 @@ def getAlgs(self):
Boundary(),
BoundingBox(),
CheckValidity(),
ConcaveHull(),
CreateAttributeIndex(),
Delaunay(),
DeleteColumn(),
Expand Down

0 comments on commit db816ec

Please sign in to comment.