QGIS API Documentation 4.3.0-Master (52aa6503bed)
Loading...
Searching...
No Matches
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
20#include "qgsgeos.h"
21#include "qgsmultipoint.h"
23#include "qgsspatialindex.h"
24#include "qgsvectorlayer.h"
25
26#include <QString>
27
28using namespace Qt::StringLiterals;
29
31
32QString QgsConcaveHullAlgorithm::name() const
33{
34 return u"concavehull"_s;
35}
36
37QString QgsConcaveHullAlgorithm::displayName() const
38{
39 return QObject::tr( "Concave hull (by layer)" );
40}
41
42QStringList QgsConcaveHullAlgorithm::tags() const
43{
44 return QObject::tr( "concave,hull,bounds,bounding" ).split( ',' );
45}
46
47QString QgsConcaveHullAlgorithm::group() const
48{
49 return QObject::tr( "Vector geometry" );
50}
51
52QString QgsConcaveHullAlgorithm::groupId() const
53{
54 return u"vectorgeometry"_s;
55}
56
57QString QgsConcaveHullAlgorithm::shortHelpString() const
58{
59 return QObject::tr( "This algorithm computes the concave hull covering all features from an input point layer." )
60 + u"\n\n"_s
61 + QObject::tr( "See the 'Concave hull (by feature)' algorithm for a concave hull calculation which covers individual features from a layer." );
62}
63
64QString QgsConcaveHullAlgorithm::shortDescription() const
65{
66 return QObject::tr( "Computes the concave hull of all features from an input point layer." );
67}
68
69QgsConcaveHullAlgorithm *QgsConcaveHullAlgorithm::createInstance() const
70{
71 return new QgsConcaveHullAlgorithm();
72}
73
74void QgsConcaveHullAlgorithm::initAlgorithm( const QVariantMap & )
75{
76 addParameter( new QgsProcessingParameterFeatureSource( u"INPUT"_s, QObject::tr( "Input layer" ), QList<int>() << static_cast<int>( Qgis::ProcessingSourceType::VectorPoint ) ) );
77 addParameter( new QgsProcessingParameterNumber( u"ALPHA"_s, QObject::tr( "Threshold (0-1, where 1 is equivalent with Convex Hull)" ), Qgis::ProcessingNumberParameterType::Double, 0.3, false, 0, 1 ) );
78 addParameter( new QgsProcessingParameterBoolean( u"HOLES"_s, QObject::tr( "Allow holes" ), true ) );
79 addParameter( new QgsProcessingParameterBoolean( u"NO_MULTIGEOMETRY"_s, QObject::tr( "Split multipart geometry into singleparts" ), false ) );
80 addParameter( new QgsProcessingParameterFeatureSink( u"OUTPUT"_s, QObject::tr( "Concave hull" ), Qgis::ProcessingSourceType::VectorPolygon ) );
81}
82
83bool QgsConcaveHullAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
84{
85 mSource.reset( parameterAsSource( parameters, u"INPUT"_s, context ) );
86 if ( !mSource )
87 throw QgsProcessingException( invalidSourceError( parameters, u"INPUT"_s ) );
88
89 if ( mSource->featureCount() < 3 )
90 throw QgsProcessingException( QObject::tr( "Input layer should contain at least 3 points." ) );
91
92 mStep = mSource->featureCount() > 0 ? 50.0 / static_cast<double>( mSource->featureCount() ) : 1;
93
94 mPercentage = parameterAsDouble( parameters, u"ALPHA"_s, context );
95 mAllowHoles = parameterAsBool( parameters, u"HOLES"_s, context );
96 mSplitMultipart = parameterAsBool( parameters, u"NO_MULTIGEOMETRY"_s, context );
97
98 return true;
99}
100
101QVariantMap QgsConcaveHullAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
102{
103 QGS_MARK_ALGORITHM_SOURCE
104
105 QString dest;
106 std::unique_ptr<QgsFeatureSink> sink( parameterAsSink( parameters, u"OUTPUT"_s, context, dest, QgsFields(), Qgis::WkbType::Polygon, mSource->sourceCrs() ) );
107 if ( !sink )
108 throw QgsProcessingException( invalidSinkError( parameters, u"OUTPUT"_s ) );
109
110#if GEOS_VERSION_NUM < GEOS_COMPUTE_VERSION( 3, 11, 0 )
111 concaveHullQgis( sink, parameters, context, feedback );
112#else
113 concaveHullGeos( sink, parameters, feedback );
114#endif
115
116 sink->finalize();
117 feedback->featureSinkFinalized( u"OUTPUT"_s );
118
119 QVariantMap outputs;
120 outputs.insert( u"OUTPUT"_s, dest );
121 return outputs;
122}
123
124void QgsConcaveHullAlgorithm::concaveHullGeos( std::unique_ptr<QgsFeatureSink> &sink, const QVariantMap &parameters, QgsProcessingFeedback *feedback )
125{
126 long long i = 0;
127
129 QgsFeature f;
130 QgsGeometry allPoints;
131 while ( it.nextFeature( f ) )
132 {
133 i++;
134 if ( feedback->isCanceled() )
135 return;
136
137 feedback->setProgress( static_cast<double>( i ) * mStep );
138
139 if ( !f.hasGeometry() )
140 continue;
141
142 const QgsAbstractGeometry *geom = f.geometry().constGet();
143 if ( QgsWkbTypes::isMultiType( geom->wkbType() ) )
144 {
146 for ( auto pit = mp.const_parts_begin(); pit != mp.const_parts_end(); ++pit )
147 {
149 }
150 }
151 else
152 {
154 }
155 }
156 const QgsGeometry concaveHull = allPoints.concaveHull( mPercentage, mAllowHoles, feedback );
157
158 if ( concaveHull.isNull() && !concaveHull.lastError().isEmpty() )
159 {
160 feedback->reportError( concaveHull.lastError() );
161 }
162
163 if ( mSplitMultipart && concaveHull.isMultipart() )
164 {
165 QVector<QgsGeometry> collection = concaveHull.asGeometryCollection();
166 mStep = collection.length() > 0 ? 50.0 / static_cast<double>( collection.length() ) : 1;
167 for ( int i = 0; i < collection.length(); i++ )
168 {
169 if ( feedback->isCanceled() )
170 {
171 break;
172 }
173
174 QgsGeometry geom = collection[i];
175 if ( !mAllowHoles )
176 {
177 geom = collection[i].removeInteriorRings();
178 }
179 QgsFeature f;
180 f.setGeometry( geom );
181 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
182 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
183
184 feedback->setProgress( 50 + i * mStep );
185 }
186 }
187 else
188 {
189 QgsGeometry geom( concaveHull );
190 if ( !mAllowHoles )
191 {
192 geom = concaveHull.removeInteriorRings();
193 }
194 QgsFeature f;
195 f.setGeometry( geom );
196 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
197 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
198 feedback->setProgress( 100 );
199 }
200}
201
202void QgsConcaveHullAlgorithm::concaveHullQgis( std::unique_ptr<QgsFeatureSink> &sink, const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
203{
204 QgsProcessingMultiStepFeedback multiStepFeedback( 5, feedback );
205
206 feedback->setProgressText( QObject::tr( "Creating Delaunay triangles…" ) );
207 multiStepFeedback.setCurrentStep( 1 );
208
209 QVariantMap params;
210 params["INPUT"] = parameters["INPUT"];
211 params["TOLERANCE"] = 0.0;
212 params["ADD_ATTRIBUTES"] = false;
213 params["OUTPUT"] = QgsProcessing::TEMPORARY_OUTPUT;
214 const QgsProcessingAlgorithm *delaunayAlg = QgsApplication::processingRegistry()->algorithmById( u"native:delaunaytriangulation"_s );
215 if ( !delaunayAlg )
216 {
217 throw QgsProcessingException( QObject::tr( "Failed to compute concave hull: Delaunay triangulation algorithm not found!" ) );
218 }
219
220 std::unique_ptr<QgsProcessingAlgorithm> algorithm;
221 algorithm.reset( delaunayAlg->create() );
222 QVariantMap results = algorithm->run( params, context, &multiStepFeedback );
223 QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( results["OUTPUT"].toString(), context ) );
224
225 if ( !layer )
226 {
227 throw QgsProcessingException( QObject::tr( "Failed to compute Delaunay triangulation." ) );
228 }
229
230 if ( layer->featureCount() == 0 )
231 {
232 throw QgsProcessingException( QObject::tr( "No Delaunay triangles created." ) );
233 }
234
235 feedback->setProgressText( QObject::tr( "Computing edges max length…" ) );
236 multiStepFeedback.setCurrentStep( 2 );
237
238 QVector<double> length;
239 QMap<long, double> edges;
240 long i = 0;
241 double step = layer->featureCount() > 0 ? 100.0 / static_cast<double>( layer->featureCount() ) : 1;
242 QgsFeatureIterator it = layer->getFeatures( QgsFeatureRequest().setNoAttributes() );
243 QgsFeature f;
244 while ( it.nextFeature( f ) )
245 {
246 i++;
247 if ( feedback->isCanceled() )
248 return;
249
250 multiStepFeedback.setProgress( static_cast<double>( i ) * step );
251
252 if ( !f.hasGeometry() )
253 continue;
254
255 QVector<QgsPointXY> points = f.geometry().asPolygon().at( 0 );
256 for ( int j = 0; j < points.size() - 1; j++ )
257 {
258 length << std::sqrt( points.at( j ).sqrDist( points.at( j + 1 ) ) );
259 }
260 QVector<double> vec = length.mid( length.size() - 3, -1 );
261 edges[f.id()] = *std::max_element( vec.constBegin(), vec.constEnd() );
262 }
263 const double maxLength = *std::max_element( length.constBegin(), length.constEnd() );
264
265 feedback->setProgressText( QObject::tr( "Removing features…" ) );
266 multiStepFeedback.setCurrentStep( 3 );
267 i = 0;
268 step = edges.size() > 0 ? 100.0 / static_cast<double>( edges.size() ) : 1;
269 QgsFeatureIds toDelete;
270 QMap<long, double>::iterator edgesIt = edges.begin();
271 while ( edgesIt != edges.end() )
272 {
273 if ( feedback->isCanceled() )
274 return;
275
276 if ( edgesIt.value() > mPercentage * maxLength )
277 {
278 toDelete << edgesIt.key();
279 }
280
281 ++edgesIt;
282 i++;
283 multiStepFeedback.setProgress( static_cast<double>( i ) * step );
284 }
285 layer->dataProvider()->deleteFeatures( toDelete );
286
287 feedback->setProgressText( QObject::tr( "Dissolving Delaunay triangles…" ) );
288 multiStepFeedback.setCurrentStep( 4 );
289 params.clear();
290 params["INPUT"] = layer->source();
291 params["OUTPUT"] = QgsProcessing::TEMPORARY_OUTPUT;
292 const QgsProcessingAlgorithm *dissolveAlg = QgsApplication::processingRegistry()->algorithmById( u"native:dissolve"_s );
293 if ( !dissolveAlg )
294 {
295 throw QgsProcessingException( QObject::tr( "Failed to compute concave hull: Dissolve algorithm not found!" ) );
296 }
297 algorithm.reset( dissolveAlg->create() );
298 results = algorithm->run( params, context, &multiStepFeedback );
299 layer = qobject_cast<QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( results["OUTPUT"].toString(), context ) );
300 if ( !layer )
301 {
302 throw QgsProcessingException( QObject::tr( "Failed to dissolve Delaunay triangles." ) );
303 }
304 if ( layer->featureCount() == 0 )
305 {
306 throw QgsProcessingException( QObject::tr( "There are no features in the dissolved layer." ) );
307 }
308
309 layer->getFeatures().nextFeature( f );
310 QgsGeometry concaveHull = f.geometry();
311
312 // save result
313 multiStepFeedback.setCurrentStep( 5 );
314
315 if ( mSplitMultipart && concaveHull.isMultipart() )
316 {
317 const QVector<QgsGeometry> collection = concaveHull.asGeometryCollection();
318 step = collection.length() > 0 ? 50.0 / static_cast<double>( collection.length() ) : 1;
319 for ( int i = 0; i < collection.length(); i++ )
320 {
321 if ( feedback->isCanceled() )
322 {
323 break;
324 }
325
326 QgsGeometry geom = collection[i];
327 if ( !mAllowHoles )
328 {
329 geom = collection[i].removeInteriorRings();
330 }
331 QgsFeature f;
332 f.setGeometry( geom );
333 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
334 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
335
336 multiStepFeedback.setProgress( i * step );
337 }
338 }
339 else
340 {
341 QgsGeometry geom( concaveHull );
342 if ( !mAllowHoles )
343 {
344 geom = concaveHull.removeInteriorRings();
345 }
346 QgsFeature f;
347 f.setGeometry( geom );
348 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
349 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
350 multiStepFeedback.setProgress( 100 );
351 }
352}
353
@ VectorPoint
Vector point layers.
Definition qgis.h:3772
@ VectorPolygon
Vector polygon layers.
Definition qgis.h:3774
@ SkipGeometryValidityChecks
Invalid geometry checks should always be skipped. This flag can be useful for algorithms which always...
Definition qgis.h:3974
@ Point
Point.
Definition qgis.h:316
@ Polygon
Polygon.
Definition qgis.h:318
@ Double
Double/float values.
Definition qgis.h:4067
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:60
QgsFeatureId id
Definition qgsfeature.h:63
QgsGeometry geometry
Definition qgsfeature.h:66
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:56
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:65
Container of fields for a vector layer.
Definition qgsfields.h:45
A geometry is the spatial representation of a feature.
QgsGeometry concaveHull(double targetPercent, bool allowHoles=false, QgsFeedback *feedback=nullptr) const
Returns a possibly concave polygon that contains all the points in the geometry.
QString lastError() const
Returns an error string referring to the last error encountered either when this geometry was created...
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.
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.
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.
void featureSinkFinalized(const QString &output)
Reports that a feature sink has been finalized.
virtual void reportError(const QString &error, bool fatalError=false)
Reports that the algorithm encountered an error while executing.
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 Q_INVOKABLE 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
T qgsgeometry_cast(QgsAbstractGeometry *geom)
QSet< QgsFeatureId > QgsFeatureIds