QGIS API Documentation 3.34.0-Prizren (ffbdd678812)
Loading...
Searching...
No Matches
qgsoverlayutils.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsoverlayutils.cpp
3 ---------------------
4 Date : April 2018
5 Copyright : (C) 2018 by Martin Dobias
6 Email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgsoverlayutils.h"
17
18#include "qgsgeometryengine.h"
19#include "qgsfeature.h"
20#include "qgsfeaturerequest.h"
21#include "qgsfeaturesource.h"
23#include "qgsspatialindex.h"
24
26
27bool QgsOverlayUtils::sanitizeIntersectionResult( QgsGeometry &geom, Qgis::GeometryType geometryType, SanitizeFlags flags )
28{
29 if ( geom.isNull() )
30 {
31 // TODO: not sure if this ever happens - if it does, that means GEOS failed badly - would be good to have a test for such situation
32 throw QgsProcessingException( QStringLiteral( "%1\n\n%2" ).arg( QObject::tr( "GEOS geoprocessing error: intersection failed." ), geom.lastError() ) );
33 }
34
35 // Intersection of geometries may give use also geometries we do not want in our results.
36 // For example, two square polygons touching at the corner have a point as the intersection, but no area.
37 // In other cases we may get a mixture of geometries in the output - we want to keep only the expected types.
39 {
40 // try to filter out irrelevant parts with different geometry type than what we want
41 geom.convertGeometryCollectionToSubclass( geometryType );
42 if ( geom.isEmpty() )
43 return false;
44 }
45
46 if ( QgsWkbTypes::geometryType( geom.wkbType() ) != geometryType )
47 {
48 // we can't make use of this resulting geometry
49 return false;
50 }
51
52 if ( geometryType != Qgis::GeometryType::Point
53 || !( flags & SanitizeFlag::DontPromotePointGeometryToMultiPoint ) )
54 {
55 // some data providers are picky about the geometries we pass to them: we can't add single-part geometries
56 // when we promised multi-part geometries, so ensure we have the right type
57 geom.convertToMultiType();
58 }
59
60 return true;
61}
62
63
65static bool sanitizeDifferenceResult( QgsGeometry &geom, Qgis::GeometryType geometryType, QgsOverlayUtils::SanitizeFlags flags )
66{
67 if ( geom.isNull() )
68 {
69 // TODO: not sure if this ever happens - if it does, that means GEOS failed badly - would be good to have a test for such situation
70 throw QgsProcessingException( QStringLiteral( "%1\n\n%2" ).arg( QObject::tr( "GEOS geoprocessing error: difference failed." ), geom.lastError() ) );
71 }
72
73 //fix geometry collections
75 {
76 // try to filter out irrelevant parts with different geometry type than what we want
77 geom.convertGeometryCollectionToSubclass( geometryType );
78 }
79
80
81 // if geomB covers the whole source geometry, we get an empty geometry collection
82 if ( geom.isEmpty() )
83 return false;
84
85 if ( geometryType != Qgis::GeometryType::Point
86 || !( flags & QgsOverlayUtils::SanitizeFlag::DontPromotePointGeometryToMultiPoint ) )
87 {
88 // some data providers are picky about the geometries we pass to them: we can't add single-part geometries
89 // when we promised multi-part geometries, so ensure we have the right type
90 geom.convertToMultiType();
91 }
92
93 return true;
94}
95
96
97static QString writeFeatureError()
98{
99 return QObject::tr( "Could not write feature" );
100}
101
102void QgsOverlayUtils::difference( const QgsFeatureSource &sourceA, const QgsFeatureSource &sourceB, QgsFeatureSink &sink, QgsProcessingContext &context, QgsProcessingFeedback *feedback, long &count, long totalCount, QgsOverlayUtils::DifferenceOutput outputAttrs, const QgsGeometryParameters &parameters, SanitizeFlags flags )
103{
105 QgsFeatureRequest requestB;
106 requestB.setNoAttributes();
107 if ( outputAttrs != OutputBA )
108 requestB.setDestinationCrs( sourceA.sourceCrs(), context.transformContext() );
109
110 double step = sourceB.featureCount() > 0 ? 100.0 / static_cast< double >( sourceB.featureCount() ) : 1;
111 long long i = 0;
112 QgsFeatureIterator fi = sourceB.getFeatures( requestB );
113
114 feedback->setProgressText( QObject::tr( "Creating spatial index" ) );
115 const QgsSpatialIndex indexB( fi, [&]( const QgsFeature & )->bool
116 {
117 i++;
118 if ( feedback->isCanceled() )
119 return false;
120
121 feedback->setProgress( static_cast< double >( i ) * step );
122
123 return true;
124 } );
125
126 if ( feedback->isCanceled() )
127 return;
128
129 const int fieldsCountA = sourceA.fields().count();
130 const int fieldsCountB = sourceB.fields().count();
131 QgsAttributes attrs;
132 attrs.resize( outputAttrs == OutputA ? fieldsCountA : ( fieldsCountA + fieldsCountB ) );
133
134 if ( totalCount == 0 )
135 totalCount = 1; // avoid division by zero
136
137 feedback->setProgressText( QObject::tr( "Calculating difference" ) );
138
139 QgsFeature featA;
140 QgsFeatureRequest requestA;
141 requestA.setInvalidGeometryCheck( context.invalidGeometryCheck() );
142 if ( outputAttrs == OutputBA )
143 requestA.setDestinationCrs( sourceB.sourceCrs(), context.transformContext() );
144 QgsFeatureIterator fitA = sourceA.getFeatures( requestA );
145 while ( fitA.nextFeature( featA ) )
146 {
147 if ( feedback->isCanceled() )
148 break;
149
150 if ( featA.hasGeometry() )
151 {
152 QgsGeometry geom( featA.geometry() );
153 const QgsFeatureIds intersects = qgis::listToSet( indexB.intersects( geom.boundingBox() ) );
154
155 QgsFeatureRequest request;
156 request.setFilterFids( intersects );
157 request.setNoAttributes();
158 if ( outputAttrs != OutputBA )
159 request.setDestinationCrs( sourceA.sourceCrs(), context.transformContext() );
160
161 std::unique_ptr< QgsGeometryEngine > engine;
162 if ( !intersects.isEmpty() )
163 {
164 // use prepared geometries for faster intersection tests
165 engine.reset( QgsGeometry::createGeometryEngine( geom.constGet() ) );
166 engine->prepareGeometry();
167 }
168
169 QVector<QgsGeometry> geometriesB;
170 QgsFeature featB;
171 QgsFeatureIterator fitB = sourceB.getFeatures( request );
172 while ( fitB.nextFeature( featB ) )
173 {
174 if ( feedback->isCanceled() )
175 break;
176
177 if ( engine->intersects( featB.geometry().constGet() ) )
178 geometriesB << featB.geometry();
179 }
180
181 if ( !geometriesB.isEmpty() )
182 {
183 const QgsGeometry geomB = QgsGeometry::unaryUnion( geometriesB, parameters );
184 if ( !geomB.lastError().isEmpty() )
185 {
186 // This may happen if input geometries from a layer do not line up well (for example polygons
187 // that are nearly touching each other, but there is a very tiny overlap or gap at one of the edges).
188 // It is possible to get rid of this issue in two steps:
189 // 1. snap geometries with a small tolerance (e.g. 1cm) using QgsGeometrySnapperSingleSource
190 // 2. fix geometries (removes polygons collapsed to lines etc.) using MakeValid
191 throw QgsProcessingException( QStringLiteral( "%1\n\n%2" ).arg( QObject::tr( "GEOS geoprocessing error: unary union failed." ), geomB.lastError() ) );
192 }
193 geom = geom.difference( geomB, parameters );
194 }
195
196 if ( !geom.isNull() && !sanitizeDifferenceResult( geom, geometryType, flags ) )
197 continue;
198
199 const QgsAttributes attrsA( featA.attributes() );
200 switch ( outputAttrs )
201 {
202 case OutputA:
203 attrs = attrsA;
204 break;
205 case OutputAB:
206 for ( int i = 0; i < fieldsCountA; ++i )
207 attrs[i] = attrsA[i];
208 break;
209 case OutputBA:
210 for ( int i = 0; i < fieldsCountA; ++i )
211 attrs[i + fieldsCountB] = attrsA[i];
212 break;
213 }
214
215 QgsFeature outFeat;
216 outFeat.setGeometry( geom );
217 outFeat.setAttributes( attrs );
218 if ( !sink.addFeature( outFeat, QgsFeatureSink::FastInsert ) )
219 throw QgsProcessingException( writeFeatureError() );
220 }
221 else
222 {
223 // TODO: should we write out features that do not have geometry?
224 if ( !sink.addFeature( featA, QgsFeatureSink::FastInsert ) )
225 throw QgsProcessingException( writeFeatureError() );
226 }
227
228 ++count;
229 feedback->setProgress( count / static_cast< double >( totalCount ) * 100. );
230 }
231}
232
233
234void QgsOverlayUtils::intersection( const QgsFeatureSource &sourceA, const QgsFeatureSource &sourceB, QgsFeatureSink &sink, QgsProcessingContext &context, QgsProcessingFeedback *feedback, long &count, long totalCount, const QList<int> &fieldIndicesA, const QList<int> &fieldIndicesB, const QgsGeometryParameters &parameters )
235{
237 const int attrCount = fieldIndicesA.count() + fieldIndicesB.count();
238
239 QgsFeatureRequest request;
240 request.setNoAttributes();
241 request.setDestinationCrs( sourceA.sourceCrs(), context.transformContext() );
242
243 QgsFeature outFeat;
244
245 double step = sourceB.featureCount() > 0 ? 100.0 / static_cast< double >( sourceB.featureCount() ) : 1;
246 long long i = 0;
247 QgsFeatureIterator fi = sourceB.getFeatures( request );
248 feedback->setProgressText( QObject::tr( "Creating spatial index" ) );
249 const QgsSpatialIndex indexB( fi, [&]( const QgsFeature & )->bool
250 {
251 i++;
252 if ( feedback->isCanceled() )
253 return false;
254
255 feedback->setProgress( static_cast< double >( i ) * step );
256
257 return true;
258 } );
259
260 if ( feedback->isCanceled() )
261 return;
262
263 if ( totalCount == 0 )
264 totalCount = 1; // avoid division by zero
265
266 feedback->setProgressText( QObject::tr( "Calculating intersection" ) );
267
268 QgsFeature featA;
269 QgsFeatureIterator fitA = sourceA.getFeatures( QgsFeatureRequest().setSubsetOfAttributes( fieldIndicesA ) );
270 while ( fitA.nextFeature( featA ) )
271 {
272 if ( feedback->isCanceled() )
273 break;
274
275 if ( !featA.hasGeometry() )
276 continue;
277
278 const QgsGeometry geom( featA.geometry() );
279 const QgsFeatureIds intersects = qgis::listToSet( indexB.intersects( geom.boundingBox() ) );
280
281 QgsFeatureRequest request;
282 request.setFilterFids( intersects );
283 request.setDestinationCrs( sourceA.sourceCrs(), context.transformContext() );
284 request.setSubsetOfAttributes( fieldIndicesB );
285
286 std::unique_ptr< QgsGeometryEngine > engine;
287 if ( !intersects.isEmpty() )
288 {
289 // use prepared geometries for faster intersection tests
290 engine.reset( QgsGeometry::createGeometryEngine( geom.constGet() ) );
291 engine->prepareGeometry();
292 }
293
294 QgsAttributes outAttributes( attrCount );
295 const QgsAttributes attrsA( featA.attributes() );
296 for ( int i = 0; i < fieldIndicesA.count(); ++i )
297 outAttributes[i] = attrsA[fieldIndicesA[i]];
298
299 QgsFeature featB;
300 QgsFeatureIterator fitB = sourceB.getFeatures( request );
301 while ( fitB.nextFeature( featB ) )
302 {
303 if ( feedback->isCanceled() )
304 break;
305
306 const QgsGeometry tmpGeom( featB.geometry() );
307 if ( !engine->intersects( tmpGeom.constGet() ) )
308 continue;
309
310 QgsGeometry intGeom = geom.intersection( tmpGeom, parameters );
311 if ( !sanitizeIntersectionResult( intGeom, geometryType ) )
312 continue;
313
314 const QgsAttributes attrsB( featB.attributes() );
315 for ( int i = 0; i < fieldIndicesB.count(); ++i )
316 outAttributes[fieldIndicesA.count() + i] = attrsB[fieldIndicesB[i]];
317
318 outFeat.setGeometry( intGeom );
319 outFeat.setAttributes( outAttributes );
320 if ( !sink.addFeature( outFeat, QgsFeatureSink::FastInsert ) )
321 throw QgsProcessingException( writeFeatureError() );
322 }
323
324 ++count;
325 feedback->setProgress( count / static_cast<double >( totalCount ) * 100. );
326 }
327}
328
329void QgsOverlayUtils::resolveOverlaps( const QgsFeatureSource &source, QgsFeatureSink &sink, QgsProcessingFeedback *feedback, const QgsGeometryParameters &parameters, SanitizeFlags flags )
330{
331 long count = 0;
332 const long totalCount = source.featureCount();
333 if ( totalCount == 0 )
334 return; // nothing to do here
335
336 QgsFeatureId newFid = -1;
337
339
340 QgsFeatureRequest requestOnlyGeoms;
341 requestOnlyGeoms.setNoAttributes();
342
343 QgsFeatureRequest requestOnlyAttrs;
344 requestOnlyAttrs.setFlags( QgsFeatureRequest::NoGeometry );
345
346 QgsFeatureRequest requestOnlyIds;
347 requestOnlyIds.setFlags( QgsFeatureRequest::NoGeometry );
348 requestOnlyIds.setNoAttributes();
349
350 // make a set of used feature IDs so that we do not try to reuse them for newly added features
351 QgsFeature f;
352 QSet<QgsFeatureId> fids;
353 QgsFeatureIterator it = source.getFeatures( requestOnlyIds );
354 while ( it.nextFeature( f ) )
355 {
356 if ( feedback->isCanceled() )
357 return;
358
359 fids.insert( f.id() );
360 }
361
362 QHash<QgsFeatureId, QgsGeometry> geometries;
363 QgsSpatialIndex index;
364 QHash<QgsFeatureId, QList<QgsFeatureId> > intersectingIds; // which features overlap a particular area
365
366 // resolve intersections
367
368 it = source.getFeatures( requestOnlyGeoms );
369 while ( it.nextFeature( f ) )
370 {
371 if ( feedback->isCanceled() )
372 return;
373
374 const QgsFeatureId fid1 = f.id();
375 QgsGeometry g1 = f.geometry();
376 std::unique_ptr< QgsGeometryEngine > g1engine;
377
378 geometries.insert( fid1, g1 );
379 index.addFeature( f );
380
381 const QgsRectangle bbox( f.geometry().boundingBox() );
382 const QList<QgsFeatureId> ids = index.intersects( bbox );
383 for ( const QgsFeatureId fid2 : ids )
384 {
385 if ( fid1 == fid2 )
386 continue;
387
388 if ( !g1engine )
389 {
390 // use prepared geometries for faster intersection tests
391 g1engine.reset( QgsGeometry::createGeometryEngine( g1.constGet() ) );
392 g1engine->prepareGeometry();
393 }
394
395 const QgsGeometry g2 = geometries.value( fid2 );
396 if ( !g1engine->intersects( g2.constGet() ) )
397 continue;
398
399 QgsGeometry geomIntersection = g1.intersection( g2, parameters );
400 if ( !sanitizeIntersectionResult( geomIntersection, geometryType ) )
401 continue;
402
403 //
404 // add intersection geometry
405 //
406
407 // figure out new fid
408 while ( fids.contains( newFid ) )
409 --newFid;
410 fids.insert( newFid );
411
412 geometries.insert( newFid, geomIntersection );
413 QgsFeature fx( newFid );
414 fx.setGeometry( geomIntersection );
415
416 index.addFeature( fx );
417
418 // figure out which feature IDs belong to this intersection. Some of the IDs can be of the newly
419 // created geometries - in such case we need to retrieve original IDs
420 QList<QgsFeatureId> lst;
421 if ( intersectingIds.contains( fid1 ) )
422 lst << intersectingIds.value( fid1 );
423 else
424 lst << fid1;
425 if ( intersectingIds.contains( fid2 ) )
426 lst << intersectingIds.value( fid2 );
427 else
428 lst << fid2;
429 intersectingIds.insert( newFid, lst );
430
431 //
432 // update f1
433 //
434
435 QgsGeometry g12 = g1.difference( g2, parameters );
436
437 index.deleteFeature( f );
438 geometries.remove( fid1 );
439
440 if ( sanitizeDifferenceResult( g12, geometryType, flags ) )
441 {
442 geometries.insert( fid1, g12 );
443
444 QgsFeature f1x( fid1 );
445 f1x.setGeometry( g12 );
446 index.addFeature( f1x );
447 }
448
449 //
450 // update f2
451 //
452
453 QgsGeometry g21 = g2.difference( g1, parameters );
454
455 QgsFeature f2old( fid2 );
456 f2old.setGeometry( g2 );
457 index.deleteFeature( f2old );
458
459 geometries.remove( fid2 );
460
461 if ( sanitizeDifferenceResult( g21, geometryType, flags ) )
462 {
463 geometries.insert( fid2, g21 );
464
465 QgsFeature f2x( fid2 );
466 f2x.setGeometry( g21 );
467 index.addFeature( f2x );
468 }
469
470 // update our temporary copy of the geometry to what is left from it
471 g1 = g12;
472 g1engine.reset();
473 }
474
475 ++count;
476 feedback->setProgress( count / static_cast< double >( totalCount ) * 100. );
477 }
478 if ( feedback->isCanceled() )
479 return;
480
481 // release some memory of structures we don't need anymore
482
483 fids.clear();
484 index = QgsSpatialIndex();
485
486 // load attributes
487
488 QHash<QgsFeatureId, QgsAttributes> attributesHash;
489 it = source.getFeatures( requestOnlyAttrs );
490 while ( it.nextFeature( f ) )
491 {
492 if ( feedback->isCanceled() )
493 return;
494
495 attributesHash.insert( f.id(), f.attributes() );
496 }
497
498 // store stuff in the sink
499
500 for ( auto i = geometries.constBegin(); i != geometries.constEnd(); ++i )
501 {
502 if ( feedback->isCanceled() )
503 return;
504
505 QgsFeature outFeature( i.key() );
506 outFeature.setGeometry( i.value() );
507
508 if ( intersectingIds.contains( i.key() ) )
509 {
510 const QList<QgsFeatureId> ids = intersectingIds.value( i.key() );
511 for ( const QgsFeatureId id : ids )
512 {
513 outFeature.setAttributes( attributesHash.value( id ) );
514 if ( !sink.addFeature( outFeature, QgsFeatureSink::FastInsert ) )
515 throw QgsProcessingException( writeFeatureError() );
516 }
517 }
518 else
519 {
520 outFeature.setAttributes( attributesHash.value( i.key() ) );
521 if ( !sink.addFeature( outFeature, QgsFeatureSink::FastInsert ) )
522 throw QgsProcessingException( writeFeatureError() );
523 }
524 }
525}
526
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition qgis.h:255
@ GeometryCollection
GeometryCollection.
A vector of attributes.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
This class wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setFilterFids(const QgsFeatureIds &fids)
Sets the feature IDs that should be fetched.
QgsFeatureRequest & setFlags(QgsFeatureRequest::Flags flags)
Sets flags that affect how features will be fetched.
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
QgsFeatureRequest & setDestinationCrs(const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context)
Sets the destination crs for feature's geometries.
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
QgsFeatureRequest & setNoAttributes()
Set that no attributes will be fetched.
QgsFeatureRequest & setInvalidGeometryCheck(InvalidGeometryCheck check)
Sets invalid geometry checking behavior.
An interface for objects which accept features via addFeature(s) methods.
virtual bool addFeature(QgsFeature &feature, QgsFeatureSink::Flags flags=QgsFeatureSink::Flags())
Adds a single feature to the sink.
@ FastInsert
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
An interface for objects which provide features via a getFeatures method.
virtual QgsFields fields() const =0
Returns the fields associated with features in the source.
virtual QgsCoordinateReferenceSystem sourceCrs() const =0
Returns the coordinate reference system for features in the source.
virtual Qgis::WkbType wkbType() const =0
Returns the geometry type for features returned by this source.
virtual QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const =0
Returns an iterator for the features in the source.
virtual long long featureCount() const =0
Returns the number of features contained in the source, or -1 if the feature count is unknown.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:56
QgsAttributes attributes
Definition qgsfeature.h:65
QgsFeatureId id
Definition qgsfeature.h:64
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
QgsGeometry geometry
Definition qgsfeature.h:67
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:54
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:63
int count() const
Returns number of items.
Encapsulates parameters under which a geometry operation is performed.
A geometry is the spatial representation of a feature.
QgsGeometry difference(const QgsGeometry &geometry, const QgsGeometryParameters &parameters=QgsGeometryParameters()) const
Returns a geometry representing the points making up this geometry that do not make up other.
QString lastError() const
Returns an error string referring to the last error encountered either when this geometry was created...
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
static QgsGeometryEngine * createGeometryEngine(const QgsAbstractGeometry *geometry)
Creates and returns a new geometry engine representing the specified geometry.
QgsGeometry intersection(const QgsGeometry &geometry, const QgsGeometryParameters &parameters=QgsGeometryParameters()) const
Returns a geometry representing the points shared by this geometry and other.
bool isEmpty() const
Returns true if the geometry is empty (eg a linestring with no vertices, or a collection with no geom...
bool convertGeometryCollectionToSubclass(Qgis::GeometryType geomType)
Converts geometry collection to a the desired geometry type subclass (multi-point,...
bool convertToMultiType()
Converts single type geometry into multitype geometry e.g.
static QgsGeometry unaryUnion(const QVector< QgsGeometry > &geometries, const QgsGeometryParameters &parameters=QgsGeometryParameters())
Compute the unary union on a list of geometries.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
Qgis::WkbType wkbType() const
Returns type of the geometry as a WKB type (point / linestring / polygon etc.)
Contains information about the context in which a processing algorithm is executed.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context.
QgsFeatureRequest::InvalidGeometryCheck invalidGeometryCheck() const
Returns the behavior used for checking invalid geometries in input layers.
Custom exception class for processing related exceptions.
Base class for providing feedback from a processing algorithm.
virtual void setProgressText(const QString &text)
Sets a progress report text string.
A rectangle specified with double values.
A spatial index for QgsFeature objects.
QList< QgsFeatureId > intersects(const QgsRectangle &rectangle) const
Returns a list of features with a bounding box which intersects the specified rectangle.
bool addFeature(QgsFeature &feature, QgsFeatureSink::Flags flags=QgsFeatureSink::Flags()) override
Adds a feature to the index.
bool deleteFeature(const QgsFeature &feature)
Removes a feature from the index.
static Qgis::GeometryType geometryType(Qgis::WkbType type)
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
static Qgis::WkbType multiType(Qgis::WkbType type)
Returns the multi type for a WKB type.
static Qgis::WkbType flatType(Qgis::WkbType type)
Returns the flat type for a WKB type.
QSet< QgsFeatureId > QgsFeatureIds
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features