QGIS API Documentation 3.99.0-Master (09f76ad7019)
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( "This algorithm creates geometries which enclose the features from an input layer.\n\n"
56 "Numerous enclosing geometry types are supported, including bounding "
57 "boxes (envelopes), oriented rectangles, circles and convex hulls.\n\n"
58 "Optionally, the features can be grouped by a field. If set, this "
59 "causes the output layer to contain one feature per grouped value with "
60 "a minimal geometry covering just the features with matching values." );
61}
62
63QString QgsMinimumBoundingGeometryAlgorithm::shortDescription() const
64{
65 return QObject::tr( "Creates geometries which enclose the features from an input layer." );
66}
67
68QgsMinimumBoundingGeometryAlgorithm *QgsMinimumBoundingGeometryAlgorithm::createInstance() const
69{
70 return new QgsMinimumBoundingGeometryAlgorithm();
71}
72
73void QgsMinimumBoundingGeometryAlgorithm::initAlgorithm( const QVariantMap & )
74{
75 addParameter( new QgsProcessingParameterFeatureSource( u"INPUT"_s, QObject::tr( "Input layer" ), QList<int>() << static_cast<int>( Qgis::ProcessingSourceType::VectorAnyGeometry ) ) );
76 addParameter( 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 ) );
77
78 QStringList geometryTypes = QStringList() << QObject::tr( "Envelope (Bounding Box)" )
79 << QObject::tr( "Minimum Oriented Rectangle" )
80 << QObject::tr( "Minimum Enclosing Circle" )
81 << QObject::tr( "Convex Hull" );
82
83 addParameter( new QgsProcessingParameterEnum( u"TYPE"_s, QObject::tr( "Geometry type" ), geometryTypes ) );
84 addParameter( new QgsProcessingParameterFeatureSink( u"OUTPUT"_s, QObject::tr( "Bounding geometry" ), Qgis::ProcessingSourceType::VectorPolygon ) );
85}
86
87QVariantMap QgsMinimumBoundingGeometryAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
88{
89 std::unique_ptr<QgsProcessingFeatureSource> source( parameterAsSource( parameters, u"INPUT"_s, context ) );
90 if ( !source )
91 {
92 throw QgsProcessingException( invalidSourceError( parameters, u"INPUT"_s ) );
93 }
94
95 const QString fieldName = parameterAsString( parameters, u"FIELD"_s, context );
96 const int geometryType = parameterAsEnum( parameters, u"TYPE"_s, context );
97 const bool useField = !fieldName.isEmpty();
98
99 int fieldIndex = -1;
100
101 QgsFields fields = QgsFields();
102 fields.append( QgsField( u"id"_s, QMetaType::Type::Int, QString(), 20 ) );
103
104 if ( useField )
105 {
106 // keep original field type, name and parameters
107 fieldIndex = source->fields().lookupField( fieldName );
108 if ( fieldIndex >= 0 )
109 {
110 fields.append( source->fields().at( fieldIndex ) );
111 }
112 }
113
114 if ( geometryType == 0 )
115 {
116 // envelope
117 fields.append( QgsField( u"width"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
118 fields.append( QgsField( u"height"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
119 fields.append( QgsField( u"area"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
120 fields.append( QgsField( u"perimeter"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
121 }
122 else if ( geometryType == 1 )
123 {
124 // oriented rectangle
125 fields.append( QgsField( u"width"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
126 fields.append( QgsField( u"height"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
127 fields.append( QgsField( u"angle"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
128 fields.append( QgsField( u"area"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
129 fields.append( QgsField( u"perimeter"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
130 }
131 else if ( geometryType == 2 )
132 {
133 // circle
134 fields.append( QgsField( u"radius"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
135 fields.append( QgsField( u"area"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
136 }
137 else if ( geometryType == 3 )
138 {
139 // convex hull
140 fields.append( QgsField( u"area"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
141 fields.append( QgsField( u"perimeter"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
142 }
143
144 QString dest;
145 std::unique_ptr<QgsFeatureSink> sink( parameterAsSink( parameters, u"OUTPUT"_s, context, dest, fields, Qgis::WkbType::Polygon, source->sourceCrs() ) );
146 if ( !sink )
147 {
148 throw QgsProcessingException( invalidSinkError( parameters, u"OUTPUT"_s ) );
149 }
150
151 if ( fieldIndex >= 0 )
152 {
153 QHash<QVariant, QVector<QgsGeometry>> geometryHash;
154 QHash<QVariant, QgsRectangle> boundsHash;
155
156 double step = source->featureCount() > 0 ? 50 / source->featureCount() : 1;
157 QgsFeatureIterator features = source->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( QList<int>() << fieldIndex ) );
158
159 QgsFeature f;
160 long long i = 0;
161 while ( features.nextFeature( f ) )
162 {
163 if ( feedback->isCanceled() )
164 break;
165
166 if ( !f.hasGeometry() )
167 continue;
168
169 QVariant fieldValue = f.attribute( fieldIndex );
170 if ( geometryType == 0 )
171 {
172 auto boundsHashIt = boundsHash.find( fieldValue );
173 if ( boundsHashIt == boundsHash.end() )
174 {
175 boundsHash.insert( fieldValue, f.geometry().boundingBox() );
176 }
177 else
178 {
179 boundsHashIt.value().combineExtentWith( f.geometry().boundingBox() );
180 }
181 }
182 else
183 {
184 auto geometryHashIt = geometryHash.find( fieldValue );
185 if ( geometryHashIt == geometryHash.end() )
186 {
187 geometryHash.insert( fieldValue, QVector<QgsGeometry>() << f.geometry() );
188 }
189 else
190 {
191 geometryHashIt.value().append( f.geometry() );
192 }
193 }
194 i++;
195 feedback->setProgress( i * step );
196 }
197
198 // bounding boxes
199 i = 0;
200 if ( geometryType == 0 )
201 {
202 step = boundsHash.size() > 0 ? 50 / boundsHash.size() : 1;
203 for ( auto it = boundsHash.constBegin(); it != boundsHash.constEnd(); ++it )
204 {
205 if ( feedback->isCanceled() )
206 break;
207
208 // envelope
209 QgsFeature feature;
210 QgsRectangle rect = it.value();
211 feature.setGeometry( QgsGeometry::fromRect( rect ) );
212 feature.setAttributes( QgsAttributes() << i << it.key() << rect.width() << rect.height() << rect.area() << rect.perimeter() );
213 if ( !sink->addFeature( feature, QgsFeatureSink::FastInsert ) )
214 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
215 i++;
216 feedback->setProgress( 50 + i * step );
217 }
218 }
219 else
220 {
221 step = geometryHash.size() > 0 ? 50 / geometryHash.size() : 1;
222 for ( auto it = geometryHash.constBegin(); it != geometryHash.constEnd(); ++it )
223 {
224 if ( feedback->isCanceled() )
225 break;
226
227 // envelope
228 QgsFeature feature = createFeature( feedback, i, geometryType, it.value(), it.key() );
229 if ( !sink->addFeature( feature, QgsFeatureSink::FastInsert ) )
230 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
231 i++;
232 feedback->setProgress( 50 + i * step );
233 }
234 }
235 }
236 else
237 {
238 double step = source->featureCount() > 0 ? 80 / source->featureCount() : 1;
239 QgsFeatureIterator features = source->getFeatures( QgsFeatureRequest().setNoAttributes() );
240
241 QVector<QgsGeometry> geometryQueue;
242 geometryQueue.reserve( source->featureCount() );
243 QgsRectangle bounds;
244
245 QgsFeature f;
246 long long i = 0;
247 while ( features.nextFeature( f ) )
248 {
249 if ( feedback->isCanceled() )
250 break;
251
252 if ( !f.hasGeometry() )
253 continue;
254
255 if ( geometryType == 0 )
256 {
257 // bounding boxes, calculate on the fly for efficiency
258 bounds.combineExtentWith( f.geometry().boundingBox() );
259 }
260 else
261 {
262 geometryQueue << f.geometry();
263 }
264 i++;
265 feedback->setProgress( i * step );
266 }
267
268 if ( !feedback->isCanceled() )
269 {
270 QgsFeature feature;
271 if ( geometryType == 0 )
272 {
273 feature.setGeometry( QgsGeometry::fromRect( bounds ) );
274 feature.setAttributes( QgsAttributes() << 0 << bounds.width() << bounds.height() << bounds.area() << bounds.perimeter() );
275 }
276 else
277 {
278 feature = createFeature( feedback, 0, geometryType, geometryQueue );
279 }
280
281 if ( !sink->addFeature( feature, QgsFeatureSink::FastInsert ) )
282 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
283 }
284 }
285 sink->finalize();
286
287 QVariantMap results;
288 results.insert( u"OUTPUT"_s, dest );
289 return results;
290}
291
292QgsFeature QgsMinimumBoundingGeometryAlgorithm::createFeature( QgsProcessingFeedback *feedback, const int featureId, const int featureType, QVector<QgsGeometry> geometries, QVariant classField )
293{
294 QgsAttributes attrs;
295 attrs << featureId;
296 if ( classField.isValid() )
297 {
298 attrs << classField;
299 }
300
301 auto multiPoint = std::make_unique<QgsMultiPoint>();
302
303 for ( auto &g : geometries )
304 {
305 if ( feedback->isCanceled() )
306 break;
307
308 for ( auto it = g.constGet()->vertices_begin(); it != g.constGet()->vertices_end(); ++it )
309 {
310 if ( feedback->isCanceled() )
311 break;
312
313 multiPoint->addGeometry( ( *it ).clone() );
314 }
315 }
316
317 QgsGeometry geometry( std::move( multiPoint ) );
318 QgsGeometry outputGeometry;
319 if ( featureType == 0 )
320 {
321 // envelope
322 QgsRectangle rect = geometry.boundingBox();
323 outputGeometry = QgsGeometry::fromRect( rect );
324 attrs << rect.width() << rect.height() << rect.area() << rect.perimeter();
325 }
326 else if ( featureType == 1 )
327 {
328 // oriented rect
329 double area, angle, width, height;
330 outputGeometry = geometry.orientedMinimumBoundingBox( area, angle, width, height );
331 attrs << width << height << angle << area << 2 * width + 2 * height;
332 }
333 else if ( featureType == 2 )
334 {
335 // circle
336 QgsPointXY center;
337 double radius;
338 outputGeometry = geometry.minimalEnclosingCircle( center, radius, 72 );
339 attrs << radius << M_PI * radius * radius;
340 }
341 else if ( featureType == 3 )
342 {
343 // convex hull
344 outputGeometry = geometry.convexHull();
345 attrs << outputGeometry.constGet()->area() << outputGeometry.constGet()->perimeter();
346 }
347
348 QgsFeature f;
349 f.setGeometry( outputGeometry );
350 f.setAttributes( attrs );
351 return f;
352}
353
@ VectorAnyGeometry
Any vector layer with geometry.
Definition qgis.h:3604
@ VectorPolygon
Vector polygon layers.
Definition qgis.h:3607
@ Polygon
Polygon.
Definition qgis.h:284
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:71
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:55
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:63
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:56
Container of fields for a vector layer.
Definition qgsfields.h:46
bool append(const QgsField &field, Qgis::FieldOrigin origin=Qgis::FieldOrigin::Provider, int originIndex=-1)
Appends a field.
Definition qgsfields.cpp:76
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.
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).