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