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