QGIS API Documentation 4.3.0-Master (d3b565c628d)
Loading...
Searching...
No Matches
qgsalgorithmminimumboundinggeometry.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsalgorithmminimumboundinggeometry.cpp
3 ---------------------
4 begin : May 2025
5 copyright : (C) 2025 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"
21
22#include <QString>
23
24using namespace Qt::StringLiterals;
25
27
28QString QgsMinimumBoundingGeometryAlgorithm::name() const
29{
30 return u"minimumboundinggeometry"_s;
31}
32
33QString QgsMinimumBoundingGeometryAlgorithm::displayName() const
34{
35 return QObject::tr( "Minimum bounding geometry" );
36}
37
38QStringList QgsMinimumBoundingGeometryAlgorithm::tags() const
39{
40 return QObject::tr( "bounding,box,bounds,envelope,minimum,oriented,rectangle,enclosing,circle,convex,hull,generalization" ).split( ',' );
41}
42
43QString QgsMinimumBoundingGeometryAlgorithm::group() const
44{
45 return QObject::tr( "Vector geometry" );
46}
47
48QString QgsMinimumBoundingGeometryAlgorithm::groupId() const
49{
50 return u"vectorgeometry"_s;
51}
52
53QString QgsMinimumBoundingGeometryAlgorithm::shortHelpString() const
54{
55 return QObject::tr(
56 "This algorithm creates geometries which enclose the features from an input layer.\n\n"
57 "Numerous enclosing geometry types are supported, including bounding "
58 "boxes (envelopes), oriented rectangles, circles and convex hulls.\n\n"
59 "Optionally, the features can be grouped by a field. If set, this "
60 "causes the output layer to contain one feature per grouped value with "
61 "a minimal geometry covering just the features with matching values."
62 );
63}
64
65QString QgsMinimumBoundingGeometryAlgorithm::shortDescription() const
66{
67 return QObject::tr( "Creates geometries which enclose the features from an input layer." );
68}
69
70QgsMinimumBoundingGeometryAlgorithm *QgsMinimumBoundingGeometryAlgorithm::createInstance() const
71{
72 return new QgsMinimumBoundingGeometryAlgorithm();
73}
74
75void QgsMinimumBoundingGeometryAlgorithm::initAlgorithm( const QVariantMap & )
76{
77 addParameter( new QgsProcessingParameterFeatureSource( u"INPUT"_s, QObject::tr( "Input layer" ), QList<int>() << static_cast<int>( Qgis::ProcessingSourceType::VectorAnyGeometry ) ) );
78 addParameter(
79 new QgsProcessingParameterField( u"FIELD"_s, QObject::tr( "Field (optional, set if features should be grouped by class)" ), QVariant(), u"INPUT"_s, Qgis::ProcessingFieldParameterDataType::Any, false, true )
80 );
81
82 QStringList geometryTypes
83 = QStringList() << QObject::tr( "Envelope (Bounding Box)" ) << QObject::tr( "Minimum Oriented Rectangle" ) << QObject::tr( "Minimum Enclosing Circle" ) << QObject::tr( "Convex Hull" );
84
85 addParameter( new QgsProcessingParameterEnum( u"TYPE"_s, QObject::tr( "Geometry type" ), geometryTypes ) );
86 addParameter( new QgsProcessingParameterFeatureSink( u"OUTPUT"_s, QObject::tr( "Bounding geometry" ), Qgis::ProcessingSourceType::VectorPolygon ) );
87}
88
89QVariantMap QgsMinimumBoundingGeometryAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
90{
91 QGS_MARK_ALGORITHM_SOURCE
92
93 std::unique_ptr<QgsProcessingFeatureSource> source( parameterAsSource( parameters, u"INPUT"_s, context ) );
94 if ( !source )
95 {
96 throw QgsProcessingException( invalidSourceError( parameters, u"INPUT"_s ) );
97 }
98
99 const QString fieldName = parameterAsString( parameters, u"FIELD"_s, context );
100 const int geometryType = parameterAsEnum( parameters, u"TYPE"_s, context );
101 const bool useField = !fieldName.isEmpty();
102
103 int fieldIndex = -1;
104
105 QgsFields fields = QgsFields();
106 fields.append( QgsField( u"id"_s, QMetaType::Type::Int, QString(), 20 ) );
107
108 if ( useField )
109 {
110 // keep original field type, name and parameters
111 fieldIndex = source->fields().lookupField( fieldName );
112 if ( fieldIndex >= 0 )
113 {
114 fields.append( source->fields().at( fieldIndex ) );
115 }
116 }
117
118 if ( geometryType == 0 )
119 {
120 // envelope
121 fields.append( QgsField( u"width"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
122 fields.append( QgsField( u"height"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
123 fields.append( QgsField( u"area"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
124 fields.append( QgsField( u"perimeter"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
125 }
126 else if ( geometryType == 1 )
127 {
128 // oriented rectangle
129 fields.append( QgsField( u"width"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
130 fields.append( QgsField( u"height"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
131 fields.append( QgsField( u"angle"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
132 fields.append( QgsField( u"area"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
133 fields.append( QgsField( u"perimeter"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
134 }
135 else if ( geometryType == 2 )
136 {
137 // circle
138 fields.append( QgsField( u"radius"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
139 fields.append( QgsField( u"area"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
140 }
141 else if ( geometryType == 3 )
142 {
143 // convex hull
144 fields.append( QgsField( u"area"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
145 fields.append( QgsField( u"perimeter"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
146 }
147
148 QString dest;
149 std::unique_ptr<QgsFeatureSink> sink( parameterAsSink( parameters, u"OUTPUT"_s, context, dest, fields, Qgis::WkbType::Polygon, source->sourceCrs() ) );
150 if ( !sink )
151 {
152 throw QgsProcessingException( invalidSinkError( parameters, u"OUTPUT"_s ) );
153 }
154
155 if ( fieldIndex >= 0 )
156 {
157 QHash<QVariant, QVector<QgsGeometry>> geometryHash;
158 QHash<QVariant, QgsRectangle> boundsHash;
159
160 double step = source->featureCount() > 0 ? 50 / source->featureCount() : 1;
161 QgsFeatureIterator features = source->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( QList<int>() << fieldIndex ) );
162
163 QgsFeature f;
164 long long i = 0;
165 while ( features.nextFeature( f ) )
166 {
167 if ( feedback->isCanceled() )
168 break;
169
170 if ( !f.hasGeometry() )
171 continue;
172
173 QVariant fieldValue = f.attribute( fieldIndex );
174 if ( geometryType == 0 )
175 {
176 auto boundsHashIt = boundsHash.find( fieldValue );
177 if ( boundsHashIt == boundsHash.end() )
178 {
179 boundsHash.insert( fieldValue, f.geometry().boundingBox() );
180 }
181 else
182 {
183 boundsHashIt.value().combineExtentWith( f.geometry().boundingBox() );
184 }
185 }
186 else
187 {
188 auto geometryHashIt = geometryHash.find( fieldValue );
189 if ( geometryHashIt == geometryHash.end() )
190 {
191 geometryHash.insert( fieldValue, QVector<QgsGeometry>() << f.geometry() );
192 }
193 else
194 {
195 geometryHashIt.value().append( f.geometry() );
196 }
197 }
198 i++;
199 feedback->setProgress( i * step );
200 }
201
202 // bounding boxes
203 i = 0;
204 if ( geometryType == 0 )
205 {
206 step = boundsHash.size() > 0 ? 50 / boundsHash.size() : 1;
207 for ( auto it = boundsHash.constBegin(); it != boundsHash.constEnd(); ++it )
208 {
209 if ( feedback->isCanceled() )
210 break;
211
212 // envelope
213 QgsFeature feature;
214 QgsRectangle rect = it.value();
215 feature.setGeometry( QgsGeometry::fromRect( rect ) );
216 feature.setAttributes( QgsAttributes() << i << it.key() << rect.width() << rect.height() << rect.area() << rect.perimeter() );
217 if ( !sink->addFeature( feature, QgsFeatureSink::FastInsert ) )
218 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
219 else
220 feedback->featureAddedToSink( u"OUTPUT"_s );
221 i++;
222 feedback->setProgress( 50 + i * step );
223 }
224 }
225 else
226 {
227 step = geometryHash.size() > 0 ? 50 / geometryHash.size() : 1;
228 for ( auto it = geometryHash.constBegin(); it != geometryHash.constEnd(); ++it )
229 {
230 if ( feedback->isCanceled() )
231 break;
232
233 // envelope
234 QgsFeature feature = createFeature( feedback, i, geometryType, it.value(), it.key() );
235 if ( !sink->addFeature( feature, QgsFeatureSink::FastInsert ) )
236 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
237 else
238 feedback->featureAddedToSink( u"OUTPUT"_s );
239 i++;
240 feedback->setProgress( 50 + i * step );
241 }
242 }
243 }
244 else
245 {
246 double step = source->featureCount() > 0 ? 80 / source->featureCount() : 1;
247 QgsFeatureIterator features = source->getFeatures( QgsFeatureRequest().setNoAttributes() );
248
249 QVector<QgsGeometry> geometryQueue;
250 geometryQueue.reserve( source->featureCount() );
251 QgsRectangle bounds;
252
253 QgsFeature f;
254 long long i = 0;
255 while ( features.nextFeature( f ) )
256 {
257 if ( feedback->isCanceled() )
258 break;
259
260 if ( !f.hasGeometry() )
261 continue;
262
263 if ( geometryType == 0 )
264 {
265 // bounding boxes, calculate on the fly for efficiency
266 bounds.combineExtentWith( f.geometry().boundingBox() );
267 }
268 else
269 {
270 geometryQueue << f.geometry();
271 }
272 i++;
273 feedback->setProgress( i * step );
274 }
275
276 if ( !feedback->isCanceled() )
277 {
278 QgsFeature feature;
279 if ( geometryType == 0 )
280 {
281 feature.setGeometry( QgsGeometry::fromRect( bounds ) );
282 feature.setAttributes( QgsAttributes() << 0 << bounds.width() << bounds.height() << bounds.area() << bounds.perimeter() );
283 }
284 else
285 {
286 feature = createFeature( feedback, 0, geometryType, geometryQueue );
287 }
288
289 if ( !sink->addFeature( feature, QgsFeatureSink::FastInsert ) )
290 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
291 else
292 feedback->featureAddedToSink( u"OUTPUT"_s );
293 }
294 }
295 sink->finalize();
296 feedback->featureSinkFinalized( u"OUTPUT"_s );
297
298 QVariantMap results;
299 results.insert( u"OUTPUT"_s, dest );
300 return results;
301}
302
303QgsFeature QgsMinimumBoundingGeometryAlgorithm::createFeature( QgsProcessingFeedback *feedback, const int featureId, const int featureType, QVector<QgsGeometry> geometries, QVariant classField )
304{
305 QgsAttributes attrs;
306 attrs << featureId;
307 if ( classField.isValid() )
308 {
309 attrs << classField;
310 }
311
312 auto multiPoint = std::make_unique<QgsMultiPoint>();
313
314 for ( auto &g : geometries )
315 {
316 if ( feedback->isCanceled() )
317 break;
318
319 for ( auto it = g.constGet()->vertices_begin(); it != g.constGet()->vertices_end(); ++it )
320 {
321 if ( feedback->isCanceled() )
322 break;
323
324 multiPoint->addGeometry( ( *it ).clone() );
325 }
326 }
327
328 QgsGeometry geometry( std::move( multiPoint ) );
329 QgsGeometry outputGeometry;
330 if ( featureType == 0 )
331 {
332 // envelope
333 QgsRectangle rect = geometry.boundingBox();
334 outputGeometry = QgsGeometry::fromRect( rect );
335 attrs << rect.width() << rect.height() << rect.area() << rect.perimeter();
336 }
337 else if ( featureType == 1 )
338 {
339 // oriented rect
340 double area, angle, width, height;
341 outputGeometry = geometry.orientedMinimumBoundingBox( area, angle, width, height );
342 attrs << width << height << angle << area << 2 * width + 2 * height;
343 }
344 else if ( featureType == 2 )
345 {
346 // circle
347 QgsPointXY center;
348 double radius;
349 outputGeometry = geometry.minimalEnclosingCircle( center, radius, 72 );
350 attrs << radius << M_PI * radius * radius;
351 }
352 else if ( featureType == 3 )
353 {
354 // convex hull
355 outputGeometry = geometry.convexHull();
356 attrs << outputGeometry.constGet()->area() << outputGeometry.constGet()->perimeter();
357 }
358
359 QgsFeature f;
360 f.setGeometry( outputGeometry );
361 f.setAttributes( attrs );
362 return f;
363}
364
@ VectorAnyGeometry
Any vector layer with geometry.
Definition qgis.h:3749
@ VectorPolygon
Vector polygon layers.
Definition qgis.h:3752
@ Polygon
Polygon.
Definition qgis.h:298
virtual double perimeter() const
Returns the planar, 2-dimensional perimeter of the geometry.
virtual double area() const
Returns the planar, 2-dimensional area of the geometry.
A vector of attributes.
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
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
QgsGeometry geometry
Definition qgsfeature.h:66
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Q_INVOKABLE QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
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
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:56
Container of fields for a vector layer.
Definition qgsfields.h:45
bool append(const QgsField &field, Qgis::FieldOrigin origin=Qgis::FieldOrigin::Provider, int originIndex=-1)
Appends a field.
Definition qgsfields.cpp:75
A geometry is the spatial representation of a feature.
static QgsGeometry fromRect(const QgsRectangle &rect)
Creates a new geometry from a QgsRectangle.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
QgsGeometry orientedMinimumBoundingBox(double &area, double &angle, double &width, double &height) const
Returns the oriented minimum bounding box for the geometry, which is the smallest (by area) rotated r...
QgsGeometry convexHull() const
Returns the smallest convex polygon that contains all the points in the geometry.
QgsGeometry minimalEnclosingCircle(QgsPointXY &center, double &radius, unsigned int segments=36) const
Returns the minimal enclosing circle for the geometry.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
Represents a 2D point.
Definition qgspointxy.h:62
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 featureAddedToSink(const QString &output)
Reports that a feature was added to the the sink associated with the specified algorithm output.
void featureSinkFinalized(const QString &output)
Reports that a feature sink has been finalized.
An enum based parameter for processing algorithms, allowing for selection from predefined values.
A feature sink output for processing algorithms.
An input feature source (such as vector layers) parameter for processing algorithms.
A vector layer or feature source field parameter for processing algorithms.
A rectangle specified with double values.
double perimeter
void combineExtentWith(const QgsRectangle &rect)
Expands the rectangle so that it covers both the original rectangle and the given rectangle.
double ANALYSIS_EXPORT angle(QgsPoint *p1, QgsPoint *p2, QgsPoint *p3, QgsPoint *p4)
Calculates the angle between two segments (in 2 dimension, z-values are ignored).