QGIS API Documentation 4.1.0-Master (5bf3c20f3c9)
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 std::unique_ptr<QgsProcessingFeatureSource> source( parameterAsSource( parameters, u"INPUT"_s, context ) );
92 if ( !source )
93 {
94 throw QgsProcessingException( invalidSourceError( parameters, u"INPUT"_s ) );
95 }
96
97 const QString fieldName = parameterAsString( parameters, u"FIELD"_s, context );
98 const int geometryType = parameterAsEnum( parameters, u"TYPE"_s, context );
99 const bool useField = !fieldName.isEmpty();
100
101 int fieldIndex = -1;
102
103 QgsFields fields = QgsFields();
104 fields.append( QgsField( u"id"_s, QMetaType::Type::Int, QString(), 20 ) );
105
106 if ( useField )
107 {
108 // keep original field type, name and parameters
109 fieldIndex = source->fields().lookupField( fieldName );
110 if ( fieldIndex >= 0 )
111 {
112 fields.append( source->fields().at( fieldIndex ) );
113 }
114 }
115
116 if ( geometryType == 0 )
117 {
118 // envelope
119 fields.append( QgsField( u"width"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
120 fields.append( QgsField( u"height"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
121 fields.append( QgsField( u"area"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
122 fields.append( QgsField( u"perimeter"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
123 }
124 else if ( geometryType == 1 )
125 {
126 // oriented rectangle
127 fields.append( QgsField( u"width"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
128 fields.append( QgsField( u"height"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
129 fields.append( QgsField( u"angle"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
130 fields.append( QgsField( u"area"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
131 fields.append( QgsField( u"perimeter"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
132 }
133 else if ( geometryType == 2 )
134 {
135 // circle
136 fields.append( QgsField( u"radius"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
137 fields.append( QgsField( u"area"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
138 }
139 else if ( geometryType == 3 )
140 {
141 // convex hull
142 fields.append( QgsField( u"area"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
143 fields.append( QgsField( u"perimeter"_s, QMetaType::Type::Double, QString(), 20, 6 ) );
144 }
145
146 QString dest;
147 std::unique_ptr<QgsFeatureSink> sink( parameterAsSink( parameters, u"OUTPUT"_s, context, dest, fields, Qgis::WkbType::Polygon, source->sourceCrs() ) );
148 if ( !sink )
149 {
150 throw QgsProcessingException( invalidSinkError( parameters, u"OUTPUT"_s ) );
151 }
152
153 if ( fieldIndex >= 0 )
154 {
155 QHash<QVariant, QVector<QgsGeometry>> geometryHash;
156 QHash<QVariant, QgsRectangle> boundsHash;
157
158 double step = source->featureCount() > 0 ? 50 / source->featureCount() : 1;
159 QgsFeatureIterator features = source->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( QList<int>() << fieldIndex ) );
160
161 QgsFeature f;
162 long long i = 0;
163 while ( features.nextFeature( f ) )
164 {
165 if ( feedback->isCanceled() )
166 break;
167
168 if ( !f.hasGeometry() )
169 continue;
170
171 QVariant fieldValue = f.attribute( fieldIndex );
172 if ( geometryType == 0 )
173 {
174 auto boundsHashIt = boundsHash.find( fieldValue );
175 if ( boundsHashIt == boundsHash.end() )
176 {
177 boundsHash.insert( fieldValue, f.geometry().boundingBox() );
178 }
179 else
180 {
181 boundsHashIt.value().combineExtentWith( f.geometry().boundingBox() );
182 }
183 }
184 else
185 {
186 auto geometryHashIt = geometryHash.find( fieldValue );
187 if ( geometryHashIt == geometryHash.end() )
188 {
189 geometryHash.insert( fieldValue, QVector<QgsGeometry>() << f.geometry() );
190 }
191 else
192 {
193 geometryHashIt.value().append( f.geometry() );
194 }
195 }
196 i++;
197 feedback->setProgress( i * step );
198 }
199
200 // bounding boxes
201 i = 0;
202 if ( geometryType == 0 )
203 {
204 step = boundsHash.size() > 0 ? 50 / boundsHash.size() : 1;
205 for ( auto it = boundsHash.constBegin(); it != boundsHash.constEnd(); ++it )
206 {
207 if ( feedback->isCanceled() )
208 break;
209
210 // envelope
211 QgsFeature feature;
212 QgsRectangle rect = it.value();
213 feature.setGeometry( QgsGeometry::fromRect( rect ) );
214 feature.setAttributes( QgsAttributes() << i << it.key() << rect.width() << rect.height() << rect.area() << rect.perimeter() );
215 if ( !sink->addFeature( feature, QgsFeatureSink::FastInsert ) )
216 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
217 i++;
218 feedback->setProgress( 50 + i * step );
219 }
220 }
221 else
222 {
223 step = geometryHash.size() > 0 ? 50 / geometryHash.size() : 1;
224 for ( auto it = geometryHash.constBegin(); it != geometryHash.constEnd(); ++it )
225 {
226 if ( feedback->isCanceled() )
227 break;
228
229 // envelope
230 QgsFeature feature = createFeature( feedback, i, geometryType, it.value(), it.key() );
231 if ( !sink->addFeature( feature, QgsFeatureSink::FastInsert ) )
232 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
233 i++;
234 feedback->setProgress( 50 + i * step );
235 }
236 }
237 }
238 else
239 {
240 double step = source->featureCount() > 0 ? 80 / source->featureCount() : 1;
241 QgsFeatureIterator features = source->getFeatures( QgsFeatureRequest().setNoAttributes() );
242
243 QVector<QgsGeometry> geometryQueue;
244 geometryQueue.reserve( source->featureCount() );
245 QgsRectangle bounds;
246
247 QgsFeature f;
248 long long i = 0;
249 while ( features.nextFeature( f ) )
250 {
251 if ( feedback->isCanceled() )
252 break;
253
254 if ( !f.hasGeometry() )
255 continue;
256
257 if ( geometryType == 0 )
258 {
259 // bounding boxes, calculate on the fly for efficiency
260 bounds.combineExtentWith( f.geometry().boundingBox() );
261 }
262 else
263 {
264 geometryQueue << f.geometry();
265 }
266 i++;
267 feedback->setProgress( i * step );
268 }
269
270 if ( !feedback->isCanceled() )
271 {
272 QgsFeature feature;
273 if ( geometryType == 0 )
274 {
275 feature.setGeometry( QgsGeometry::fromRect( bounds ) );
276 feature.setAttributes( QgsAttributes() << 0 << bounds.width() << bounds.height() << bounds.area() << bounds.perimeter() );
277 }
278 else
279 {
280 feature = createFeature( feedback, 0, geometryType, geometryQueue );
281 }
282
283 if ( !sink->addFeature( feature, QgsFeatureSink::FastInsert ) )
284 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
285 }
286 }
287 sink->finalize();
288
289 QVariantMap results;
290 results.insert( u"OUTPUT"_s, dest );
291 return results;
292}
293
294QgsFeature QgsMinimumBoundingGeometryAlgorithm::createFeature( QgsProcessingFeedback *feedback, const int featureId, const int featureType, QVector<QgsGeometry> geometries, QVariant classField )
295{
296 QgsAttributes attrs;
297 attrs << featureId;
298 if ( classField.isValid() )
299 {
300 attrs << classField;
301 }
302
303 auto multiPoint = std::make_unique<QgsMultiPoint>();
304
305 for ( auto &g : geometries )
306 {
307 if ( feedback->isCanceled() )
308 break;
309
310 for ( auto it = g.constGet()->vertices_begin(); it != g.constGet()->vertices_end(); ++it )
311 {
312 if ( feedback->isCanceled() )
313 break;
314
315 multiPoint->addGeometry( ( *it ).clone() );
316 }
317 }
318
319 QgsGeometry geometry( std::move( multiPoint ) );
320 QgsGeometry outputGeometry;
321 if ( featureType == 0 )
322 {
323 // envelope
324 QgsRectangle rect = geometry.boundingBox();
325 outputGeometry = QgsGeometry::fromRect( rect );
326 attrs << rect.width() << rect.height() << rect.area() << rect.perimeter();
327 }
328 else if ( featureType == 1 )
329 {
330 // oriented rect
331 double area, angle, width, height;
332 outputGeometry = geometry.orientedMinimumBoundingBox( area, angle, width, height );
333 attrs << width << height << angle << area << 2 * width + 2 * height;
334 }
335 else if ( featureType == 2 )
336 {
337 // circle
338 QgsPointXY center;
339 double radius;
340 outputGeometry = geometry.minimalEnclosingCircle( center, radius, 72 );
341 attrs << radius << M_PI * radius * radius;
342 }
343 else if ( featureType == 3 )
344 {
345 // convex hull
346 outputGeometry = geometry.convexHull();
347 attrs << outputGeometry.constGet()->area() << outputGeometry.constGet()->perimeter();
348 }
349
350 QgsFeature f;
351 f.setGeometry( outputGeometry );
352 f.setAttributes( attrs );
353 return f;
354}
355
@ VectorAnyGeometry
Any vector layer with geometry.
Definition qgis.h:3647
@ VectorPolygon
Vector polygon layers.
Definition qgis.h:3650
@ 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: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: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:46
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.
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).