QGIS API Documentation 3.43.0-Master (e01d6d7c4c0)
qgsalgorithmconcavehull.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsalgorithmconcavehull.cpp
3 ---------------------
4 begin : July 2023
5 copyright : (C) 2023 by Alexander Bruy
6 email : alexander dot bruy at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
19#include "qgsspatialindex.h"
20#include "qgsmultipoint.h"
22#include "qgsvectorlayer.h"
23
25
26QString QgsConcaveHullAlgorithm::name() const
27{
28 return QStringLiteral( "concavehull" );
29}
30
31QString QgsConcaveHullAlgorithm::displayName() const
32{
33 return QObject::tr( "Concave hull" );
34}
35
36QStringList QgsConcaveHullAlgorithm::tags() const
37{
38 return QObject::tr( "concave,hull,bounds,bounding" ).split( ',' );
39}
40
41QString QgsConcaveHullAlgorithm::group() const
42{
43 return QObject::tr( "Vector geometry" );
44}
45
46QString QgsConcaveHullAlgorithm::groupId() const
47{
48 return QStringLiteral( "vectorgeometry" );
49}
50
51QString QgsConcaveHullAlgorithm::shortHelpString() const
52{
53 return QObject::tr( "This algorithm computes the concave hull of the features from an input layer." );
54}
55
56QString QgsConcaveHullAlgorithm::shortDescription() const
57{
58 return QObject::tr( "Computes the concave hull of the features from an input layer." );
59}
60
61QgsConcaveHullAlgorithm *QgsConcaveHullAlgorithm::createInstance() const
62{
63 return new QgsConcaveHullAlgorithm();
64}
65
66void QgsConcaveHullAlgorithm::initAlgorithm( const QVariantMap & )
67{
68 addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ), QList<int>() << static_cast<int>( Qgis::ProcessingSourceType::VectorPoint ) ) );
69 addParameter( new QgsProcessingParameterNumber( QStringLiteral( "ALPHA" ), QObject::tr( "Threshold (0-1, where 1 is equivalent with Convex Hull)" ), Qgis::ProcessingNumberParameterType::Double, 0.3, false, 0, 1 ) );
70 addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "HOLES" ), QObject::tr( "Allow holes" ), true ) );
71 addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "NO_MULTIGEOMETRY" ), QObject::tr( "Split multipart geometry into singleparts" ), false ) );
72 addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Concave hull" ), Qgis::ProcessingSourceType::VectorPolygon ) );
73}
74
75bool QgsConcaveHullAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
76{
77 mSource.reset( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
78 if ( !mSource )
79 throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );
80
81 if ( mSource->featureCount() < 3 )
82 throw QgsProcessingException( QObject::tr( "Input layer should contain at least 3 points." ) );
83
84 mStep = mSource->featureCount() > 0 ? 50.0 / mSource->featureCount() : 1;
85
86 mPercentage = parameterAsDouble( parameters, QStringLiteral( "ALPHA" ), context );
87 mAllowHoles = parameterAsBool( parameters, QStringLiteral( "HOLES" ), context );
88 mSplitMultipart = parameterAsBool( parameters, QStringLiteral( "NO_MULTIGEOMETRY" ), context );
89
90 return true;
91}
92
93QVariantMap QgsConcaveHullAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
94{
95 QString dest;
96 std::unique_ptr<QgsFeatureSink> sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, QgsFields(), Qgis::WkbType::Polygon, mSource->sourceCrs() ) );
97 if ( !sink )
98 throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
99
100#if GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR < 11
101 concaveHullQgis( sink, parameters, context, feedback );
102#else
103 concaveHullGeos( sink, parameters, feedback );
104#endif
105
106 sink->finalize();
107
108 QVariantMap outputs;
109 outputs.insert( QStringLiteral( "OUTPUT" ), dest );
110 return outputs;
111}
112
113void QgsConcaveHullAlgorithm::concaveHullGeos( std::unique_ptr<QgsFeatureSink> &sink, const QVariantMap &parameters, QgsProcessingFeedback *feedback )
114{
115 long long i = 0;
116
118 QgsFeature f;
119 QgsGeometry allPoints;
120 while ( it.nextFeature( f ) )
121 {
122 i++;
123 if ( feedback->isCanceled() )
124 return;
125
126 feedback->setProgress( i * mStep );
127
128 if ( !f.hasGeometry() )
129 continue;
130
131 const QgsAbstractGeometry *geom = f.geometry().constGet();
132 if ( QgsWkbTypes::isMultiType( geom->wkbType() ) )
133 {
134 const QgsMultiPoint mp( *qgsgeometry_cast<const QgsMultiPoint *>( geom ) );
135 for ( auto pit = mp.const_parts_begin(); pit != mp.const_parts_end(); ++pit )
136 {
137 allPoints.addPartV2( qgsgeometry_cast<const QgsPoint *>( *pit )->clone(), Qgis::WkbType::Point );
138 }
139 }
140 else
141 {
142 allPoints.addPartV2( qgsgeometry_cast<const QgsPoint *>( geom )->clone(), Qgis::WkbType::Point );
143 }
144 }
145 const QgsGeometry concaveHull = allPoints.concaveHull( mPercentage, mAllowHoles );
146
147 if ( mSplitMultipart && concaveHull.isMultipart() )
148 {
149 QVector<QgsGeometry> collection = concaveHull.asGeometryCollection();
150 mStep = collection.length() > 0 ? 50.0 / collection.length() : 1;
151 for ( int i = 0; i < collection.length(); i++ )
152 {
153 if ( feedback->isCanceled() )
154 {
155 break;
156 }
157
158 QgsGeometry geom = collection[i];
159 if ( !mAllowHoles )
160 {
161 geom = collection[i].removeInteriorRings();
162 }
163 QgsFeature f;
164 f.setGeometry( geom );
165 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
166 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
167
168 feedback->setProgress( 50 + i * mStep );
169 }
170 }
171 else
172 {
173 QgsGeometry geom( concaveHull );
174 if ( !mAllowHoles )
175 {
176 geom = concaveHull.removeInteriorRings();
177 }
178 QgsFeature f;
179 f.setGeometry( geom );
180 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
181 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
182 feedback->setProgress( 100 );
183 }
184}
185
186void QgsConcaveHullAlgorithm::concaveHullQgis( std::unique_ptr<QgsFeatureSink> &sink, const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
187{
188 QgsProcessingMultiStepFeedback multiStepFeedback( 5, feedback );
189
190 feedback->setProgressText( QObject::tr( "Creating Delaunay triangles…" ) );
191 multiStepFeedback.setCurrentStep( 1 );
192
193 QVariantMap params;
194 params["INPUT"] = parameters["INPUT"];
195 params["TOLERANCE"] = 0.0;
196 params["ADD_ATTRIBUTES"] = false;
197 params["OUTPUT"] = QgsProcessing::TEMPORARY_OUTPUT;
198 const QgsProcessingAlgorithm *delaunayAlg = QgsApplication::processingRegistry()->algorithmById( QStringLiteral( "native:delaunaytriangulation" ) );
199 if ( !delaunayAlg )
200 {
201 throw QgsProcessingException( QObject::tr( "Failed to compute concave hull: Delaunay triangulation algorithm not found!" ) );
202 }
203
204 std::unique_ptr<QgsProcessingAlgorithm> algorithm;
205 algorithm.reset( delaunayAlg->create() );
206 QVariantMap results = algorithm->run( params, context, &multiStepFeedback );
207 QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( results["OUTPUT"].toString(), context ) );
208
209 if ( !layer )
210 {
211 throw QgsProcessingException( QObject::tr( "Failed to compute Delaunay triangulation." ) );
212 }
213
214 if ( layer->featureCount() == 0 )
215 {
216 throw QgsProcessingException( QObject::tr( "No Delaunay triangles created." ) );
217 }
218
219 feedback->setProgressText( QObject::tr( "Computing edges max length…" ) );
220 multiStepFeedback.setCurrentStep( 2 );
221
222 QVector<double> length;
223 QMap<long, double> edges;
224 long i = 0;
225 double step = layer->featureCount() > 0 ? 100.0 / layer->featureCount() : 1;
226 QgsFeatureIterator it = layer->getFeatures( QgsFeatureRequest().setNoAttributes() );
227 QgsFeature f;
228 while ( it.nextFeature( f ) )
229 {
230 i++;
231 if ( feedback->isCanceled() )
232 return;
233
234 multiStepFeedback.setProgress( i * step );
235
236 if ( !f.hasGeometry() )
237 continue;
238
239 QVector<QgsPointXY> points = f.geometry().asPolygon().at( 0 );
240 for ( int j = 0; j < points.size() - 1; j++ )
241 {
242 length << std::sqrt( points.at( j ).sqrDist( points.at( j + 1 ) ) );
243 }
244 QVector<double> vec = length.mid( length.size() - 3, -1 );
245 edges[f.id()] = *std::max_element( vec.constBegin(), vec.constEnd() );
246 }
247 const double maxLength = *std::max_element( length.constBegin(), length.constEnd() );
248
249 feedback->setProgressText( QObject::tr( "Removing features…" ) );
250 multiStepFeedback.setCurrentStep( 3 );
251 i = 0;
252 step = edges.size() > 0 ? 100.0 / edges.size() : 1;
253 QgsFeatureIds toDelete;
254 QMap<long, double>::iterator edgesIt = edges.begin();
255 while ( edgesIt != edges.end() )
256 {
257 if ( feedback->isCanceled() )
258 return;
259
260 if ( edgesIt.value() > mPercentage * maxLength )
261 {
262 toDelete << edgesIt.key();
263 }
264
265 ++edgesIt;
266 i++;
267 multiStepFeedback.setProgress( i * step );
268 }
269 layer->dataProvider()->deleteFeatures( toDelete );
270
271 feedback->setProgressText( QObject::tr( "Dissolving Delaunay triangles…" ) );
272 multiStepFeedback.setCurrentStep( 4 );
273 params.clear();
274 params["INPUT"] = layer->source();
275 params["OUTPUT"] = QgsProcessing::TEMPORARY_OUTPUT;
276 const QgsProcessingAlgorithm *dissolveAlg = QgsApplication::processingRegistry()->algorithmById( QStringLiteral( "native:dissolve" ) );
277 if ( !dissolveAlg )
278 {
279 throw QgsProcessingException( QObject::tr( "Failed to compute concave hull: Dissolve algorithm not found!" ) );
280 }
281 algorithm.reset( dissolveAlg->create() );
282 results = algorithm->run( params, context, &multiStepFeedback );
283 layer = qobject_cast<QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( results["OUTPUT"].toString(), context ) );
284 if ( !layer )
285 {
286 throw QgsProcessingException( QObject::tr( "Failed to dissolve Delaunay triangles." ) );
287 }
288 if ( layer->featureCount() == 0 )
289 {
290 throw QgsProcessingException( QObject::tr( "There are no features in the dissolved layer." ) );
291 }
292
293 layer->getFeatures().nextFeature( f );
294 QgsGeometry concaveHull = f.geometry();
295
296 // save result
297 multiStepFeedback.setCurrentStep( 5 );
298
299 if ( mSplitMultipart && concaveHull.isMultipart() )
300 {
301 const QVector<QgsGeometry> collection = concaveHull.asGeometryCollection();
302 step = collection.length() > 0 ? 50.0 / collection.length() : 1;
303 for ( int i = 0; i < collection.length(); i++ )
304 {
305 if ( feedback->isCanceled() )
306 {
307 break;
308 }
309
310 QgsGeometry geom = collection[i];
311 if ( !mAllowHoles )
312 {
313 geom = collection[i].removeInteriorRings();
314 }
315 QgsFeature f;
316 f.setGeometry( geom );
317 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
318 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
319
320 multiStepFeedback.setProgress( i * step );
321 }
322 }
323 else
324 {
325 QgsGeometry geom( concaveHull );
326 if ( !mAllowHoles )
327 {
328 geom = concaveHull.removeInteriorRings();
329 }
330 QgsFeature f;
331 f.setGeometry( geom );
332 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
333 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
334 multiStepFeedback.setProgress( 100 );
335 }
336}
337
@ VectorPoint
Vector point layers.
@ VectorPolygon
Vector polygon layers.
@ SkipGeometryValidityChecks
Invalid geometry checks should always be skipped. This flag can be useful for algorithms which always...
@ Polygon
Polygon.
Abstract base class for all geometries.
Qgis::WkbType wkbType() const
Returns the WKB type of the geometry.
static QgsProcessingRegistry * processingRegistry()
Returns the application's processing registry, used for managing processing providers,...
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
Wraps a request for features to a vector layer (or directly its vector data provider).
@ FastInsert
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:58
QgsFeatureId id
Definition qgsfeature.h:66
QgsGeometry geometry
Definition qgsfeature.h:69
bool hasGeometry() const
Returns true if the feature has an associated geometry.
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:53
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:61
Container of fields for a vector layer.
Definition qgsfields.h:46
A geometry is the spatial representation of a feature.
QgsPolygonXY asPolygon() const
Returns the contents of the geometry as a polygon.
QVector< QgsGeometry > asGeometryCollection() const
Returns contents of the geometry as a list of geometries.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
bool isMultipart() const
Returns true if WKB of the geometry is of WKBMulti* type.
QgsGeometry concaveHull(double targetPercent, bool allowHoles=false) const
Returns a possibly concave polygon that contains all the points in the geometry.
Qgis::GeometryOperationResult addPartV2(const QVector< QgsPointXY > &points, Qgis::WkbType wkbType=Qgis::WkbType::Unknown)
Adds a new part to a the geometry.
QgsGeometry removeInteriorRings(double minimumAllowedArea=-1) const
Removes the interior rings from a (multi)polygon geometry.
QString source() const
Returns the source for the layer.
Multi point geometry collection.
Abstract base class for processing algorithms.
QVariantMap run(const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback, bool *ok=nullptr, const QVariantMap &configuration=QVariantMap(), bool catchExceptions=true) const
Executes the algorithm using the specified parameters.
QgsProcessingAlgorithm * create(const QVariantMap &configuration=QVariantMap()) const
Creates a copy of the algorithm, ready for execution.
Contains information about the context in which a processing algorithm is executed.
Custom exception class for processing related exceptions.
Base class for providing feedback from a processing algorithm.
virtual void setProgressText(const QString &text)
Sets a progress report text string.
Processing feedback object for multi-step operations.
A boolean parameter for processing algorithms.
A feature sink output for processing algorithms.
An input feature source (such as vector layers) parameter for processing algorithms.
A numeric parameter for processing algorithms.
const QgsProcessingAlgorithm * algorithmById(const QString &id) const
Finds an algorithm by its ID.
static QgsMapLayer * mapLayerFromString(const QString &string, QgsProcessingContext &context, bool allowLoadingNewLayers=true, QgsProcessingUtils::LayerHint typeHint=QgsProcessingUtils::LayerHint::UnknownType, QgsProcessing::LayerOptionsFlags flags=QgsProcessing::LayerOptionsFlags())
Interprets a string as a map layer within the supplied context.
static const QString TEMPORARY_OUTPUT
Constant used to indicate that a Processing algorithm output should be a temporary layer/file.
virtual bool deleteFeatures(const QgsFeatureIds &id)
Deletes one or more features from the provider.
Represents a vector layer which manages a vector based dataset.
long long featureCount(const QString &legendKey) const
Number of features rendered with specified legend key.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
QgsVectorDataProvider * dataProvider() FINAL
Returns the layer's data provider, it may be nullptr.
static bool isMultiType(Qgis::WkbType type)
Returns true if the WKB type is a multi type.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into allowing algorithms to be written in pure substantial changes are required in order to port existing x Processing algorithms for QGIS x The most significant changes are outlined not GeoAlgorithm For algorithms which operate on features one by consider subclassing the QgsProcessingFeatureBasedAlgorithm class This class allows much of the boilerplate code for looping over features from a vector layer to be bypassed and instead requires implementation of a processFeature method Ensure that your algorithm(or algorithm 's parent class) implements the new pure virtual createInstance(self) call
QSet< QgsFeatureId > QgsFeatureIds