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