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