QGIS API Documentation 3.43.0-Master (6c62b930b02)
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 (by layer)" );
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 covering all features from an input point layer." ) + QStringLiteral( "\n\n" )
54 + QObject::tr( "See the 'Concave hull (by feature)' algorithm for a concave hull calculation which covers individual features from a layer." );
55}
56
57QString QgsConcaveHullAlgorithm::shortDescription() const
58{
59 return QObject::tr( "Computes the concave hull of all features from an input point layer." );
60}
61
62QgsConcaveHullAlgorithm *QgsConcaveHullAlgorithm::createInstance() const
63{
64 return new QgsConcaveHullAlgorithm();
65}
66
67void QgsConcaveHullAlgorithm::initAlgorithm( const QVariantMap & )
68{
69 addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ), QList<int>() << static_cast<int>( Qgis::ProcessingSourceType::VectorPoint ) ) );
70 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 ) );
71 addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "HOLES" ), QObject::tr( "Allow holes" ), true ) );
72 addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "NO_MULTIGEOMETRY" ), QObject::tr( "Split multipart geometry into singleparts" ), false ) );
73 addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Concave hull" ), Qgis::ProcessingSourceType::VectorPolygon ) );
74}
75
76bool QgsConcaveHullAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
77{
78 mSource.reset( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
79 if ( !mSource )
80 throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );
81
82 if ( mSource->featureCount() < 3 )
83 throw QgsProcessingException( QObject::tr( "Input layer should contain at least 3 points." ) );
84
85 mStep = mSource->featureCount() > 0 ? 50.0 / mSource->featureCount() : 1;
86
87 mPercentage = parameterAsDouble( parameters, QStringLiteral( "ALPHA" ), context );
88 mAllowHoles = parameterAsBool( parameters, QStringLiteral( "HOLES" ), context );
89 mSplitMultipart = parameterAsBool( parameters, QStringLiteral( "NO_MULTIGEOMETRY" ), context );
90
91 return true;
92}
93
94QVariantMap QgsConcaveHullAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
95{
96 QString dest;
97 std::unique_ptr<QgsFeatureSink> sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, QgsFields(), Qgis::WkbType::Polygon, mSource->sourceCrs() ) );
98 if ( !sink )
99 throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
100
101#if GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR < 11
102 concaveHullQgis( sink, parameters, context, feedback );
103#else
104 concaveHullGeos( sink, parameters, feedback );
105#endif
106
107 sink->finalize();
108
109 QVariantMap outputs;
110 outputs.insert( QStringLiteral( "OUTPUT" ), dest );
111 return outputs;
112}
113
114void QgsConcaveHullAlgorithm::concaveHullGeos( std::unique_ptr<QgsFeatureSink> &sink, const QVariantMap &parameters, QgsProcessingFeedback *feedback )
115{
116 long long i = 0;
117
119 QgsFeature f;
120 QgsGeometry allPoints;
121 while ( it.nextFeature( f ) )
122 {
123 i++;
124 if ( feedback->isCanceled() )
125 return;
126
127 feedback->setProgress( i * mStep );
128
129 if ( !f.hasGeometry() )
130 continue;
131
132 const QgsAbstractGeometry *geom = f.geometry().constGet();
133 if ( QgsWkbTypes::isMultiType( geom->wkbType() ) )
134 {
135 const QgsMultiPoint mp( *qgsgeometry_cast<const QgsMultiPoint *>( geom ) );
136 for ( auto pit = mp.const_parts_begin(); pit != mp.const_parts_end(); ++pit )
137 {
138 allPoints.addPartV2( qgsgeometry_cast<const QgsPoint *>( *pit )->clone(), Qgis::WkbType::Point );
139 }
140 }
141 else
142 {
143 allPoints.addPartV2( qgsgeometry_cast<const QgsPoint *>( geom )->clone(), Qgis::WkbType::Point );
144 }
145 }
146 const QgsGeometry concaveHull = allPoints.concaveHull( mPercentage, mAllowHoles );
147
148 if ( mSplitMultipart && concaveHull.isMultipart() )
149 {
150 QVector<QgsGeometry> collection = concaveHull.asGeometryCollection();
151 mStep = collection.length() > 0 ? 50.0 / collection.length() : 1;
152 for ( int i = 0; i < collection.length(); i++ )
153 {
154 if ( feedback->isCanceled() )
155 {
156 break;
157 }
158
159 QgsGeometry geom = collection[i];
160 if ( !mAllowHoles )
161 {
162 geom = collection[i].removeInteriorRings();
163 }
164 QgsFeature f;
165 f.setGeometry( geom );
166 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
167 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
168
169 feedback->setProgress( 50 + i * mStep );
170 }
171 }
172 else
173 {
174 QgsGeometry geom( concaveHull );
175 if ( !mAllowHoles )
176 {
177 geom = concaveHull.removeInteriorRings();
178 }
179 QgsFeature f;
180 f.setGeometry( geom );
181 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
182 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
183 feedback->setProgress( 100 );
184 }
185}
186
187void QgsConcaveHullAlgorithm::concaveHullQgis( std::unique_ptr<QgsFeatureSink> &sink, const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
188{
189 QgsProcessingMultiStepFeedback multiStepFeedback( 5, feedback );
190
191 feedback->setProgressText( QObject::tr( "Creating Delaunay triangles…" ) );
192 multiStepFeedback.setCurrentStep( 1 );
193
194 QVariantMap params;
195 params["INPUT"] = parameters["INPUT"];
196 params["TOLERANCE"] = 0.0;
197 params["ADD_ATTRIBUTES"] = false;
198 params["OUTPUT"] = QgsProcessing::TEMPORARY_OUTPUT;
199 const QgsProcessingAlgorithm *delaunayAlg = QgsApplication::processingRegistry()->algorithmById( QStringLiteral( "native:delaunaytriangulation" ) );
200 if ( !delaunayAlg )
201 {
202 throw QgsProcessingException( QObject::tr( "Failed to compute concave hull: Delaunay triangulation algorithm not found!" ) );
203 }
204
205 std::unique_ptr<QgsProcessingAlgorithm> algorithm;
206 algorithm.reset( delaunayAlg->create() );
207 QVariantMap results = algorithm->run( params, context, &multiStepFeedback );
208 QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( results["OUTPUT"].toString(), context ) );
209
210 if ( !layer )
211 {
212 throw QgsProcessingException( QObject::tr( "Failed to compute Delaunay triangulation." ) );
213 }
214
215 if ( layer->featureCount() == 0 )
216 {
217 throw QgsProcessingException( QObject::tr( "No Delaunay triangles created." ) );
218 }
219
220 feedback->setProgressText( QObject::tr( "Computing edges max length…" ) );
221 multiStepFeedback.setCurrentStep( 2 );
222
223 QVector<double> length;
224 QMap<long, double> edges;
225 long i = 0;
226 double step = layer->featureCount() > 0 ? 100.0 / layer->featureCount() : 1;
227 QgsFeatureIterator it = layer->getFeatures( QgsFeatureRequest().setNoAttributes() );
228 QgsFeature f;
229 while ( it.nextFeature( f ) )
230 {
231 i++;
232 if ( feedback->isCanceled() )
233 return;
234
235 multiStepFeedback.setProgress( i * step );
236
237 if ( !f.hasGeometry() )
238 continue;
239
240 QVector<QgsPointXY> points = f.geometry().asPolygon().at( 0 );
241 for ( int j = 0; j < points.size() - 1; j++ )
242 {
243 length << std::sqrt( points.at( j ).sqrDist( points.at( j + 1 ) ) );
244 }
245 QVector<double> vec = length.mid( length.size() - 3, -1 );
246 edges[f.id()] = *std::max_element( vec.constBegin(), vec.constEnd() );
247 }
248 const double maxLength = *std::max_element( length.constBegin(), length.constEnd() );
249
250 feedback->setProgressText( QObject::tr( "Removing features…" ) );
251 multiStepFeedback.setCurrentStep( 3 );
252 i = 0;
253 step = edges.size() > 0 ? 100.0 / edges.size() : 1;
254 QgsFeatureIds toDelete;
255 QMap<long, double>::iterator edgesIt = edges.begin();
256 while ( edgesIt != edges.end() )
257 {
258 if ( feedback->isCanceled() )
259 return;
260
261 if ( edgesIt.value() > mPercentage * maxLength )
262 {
263 toDelete << edgesIt.key();
264 }
265
266 ++edgesIt;
267 i++;
268 multiStepFeedback.setProgress( i * step );
269 }
270 layer->dataProvider()->deleteFeatures( toDelete );
271
272 feedback->setProgressText( QObject::tr( "Dissolving Delaunay triangles…" ) );
273 multiStepFeedback.setCurrentStep( 4 );
274 params.clear();
275 params["INPUT"] = layer->source();
276 params["OUTPUT"] = QgsProcessing::TEMPORARY_OUTPUT;
277 const QgsProcessingAlgorithm *dissolveAlg = QgsApplication::processingRegistry()->algorithmById( QStringLiteral( "native:dissolve" ) );
278 if ( !dissolveAlg )
279 {
280 throw QgsProcessingException( QObject::tr( "Failed to compute concave hull: Dissolve algorithm not found!" ) );
281 }
282 algorithm.reset( dissolveAlg->create() );
283 results = algorithm->run( params, context, &multiStepFeedback );
284 layer = qobject_cast<QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( results["OUTPUT"].toString(), context ) );
285 if ( !layer )
286 {
287 throw QgsProcessingException( QObject::tr( "Failed to dissolve Delaunay triangles." ) );
288 }
289 if ( layer->featureCount() == 0 )
290 {
291 throw QgsProcessingException( QObject::tr( "There are no features in the dissolved layer." ) );
292 }
293
294 layer->getFeatures().nextFeature( f );
295 QgsGeometry concaveHull = f.geometry();
296
297 // save result
298 multiStepFeedback.setCurrentStep( 5 );
299
300 if ( mSplitMultipart && concaveHull.isMultipart() )
301 {
302 const QVector<QgsGeometry> collection = concaveHull.asGeometryCollection();
303 step = collection.length() > 0 ? 50.0 / collection.length() : 1;
304 for ( int i = 0; i < collection.length(); i++ )
305 {
306 if ( feedback->isCanceled() )
307 {
308 break;
309 }
310
311 QgsGeometry geom = collection[i];
312 if ( !mAllowHoles )
313 {
314 geom = collection[i].removeInteriorRings();
315 }
316 QgsFeature f;
317 f.setGeometry( geom );
318 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
319 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
320
321 multiStepFeedback.setProgress( i * step );
322 }
323 }
324 else
325 {
326 QgsGeometry geom( concaveHull );
327 if ( !mAllowHoles )
328 {
329 geom = concaveHull.removeInteriorRings();
330 }
331 QgsFeature f;
332 f.setGeometry( geom );
333 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
334 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
335 multiStepFeedback.setProgress( 100 );
336 }
337}
338
@ 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