QGIS API Documentation 3.41.0-Master (cea29feecf2)
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 i++;
117 if ( feedback->isCanceled() )
118 return false;
119
120 feedback->setProgress( static_cast<double>( i ) * step );
121
122 return true;
123 } );
124
125 if ( feedback->isCanceled() )
126 return;
127
128 const int fieldsCountA = sourceA.fields().count();
129 const int fieldsCountB = sourceB.fields().count();
130 QgsAttributes attrs;
131 attrs.resize( outputAttrs == OutputA ? fieldsCountA : ( fieldsCountA + fieldsCountB ) );
132
133 if ( totalCount == 0 )
134 totalCount = 1; // avoid division by zero
135
136 feedback->setProgressText( QObject::tr( "Calculating difference" ) );
137
138 QgsFeature featA;
139 QgsFeatureRequest requestA;
140 requestA.setInvalidGeometryCheck( context.invalidGeometryCheck() );
141 if ( outputAttrs == OutputBA )
142 requestA.setDestinationCrs( sourceB.sourceCrs(), context.transformContext() );
143 QgsFeatureIterator fitA = sourceA.getFeatures( requestA );
144 while ( fitA.nextFeature( featA ) )
145 {
146 if ( feedback->isCanceled() )
147 break;
148
149 if ( featA.hasGeometry() )
150 {
151 QgsGeometry geom( featA.geometry() );
152 const QgsFeatureIds intersects = qgis::listToSet( indexB.intersects( geom.boundingBox() ) );
153
154 QgsFeatureRequest request;
155 request.setFilterFids( intersects );
156 request.setNoAttributes();
157 if ( outputAttrs != OutputBA )
158 request.setDestinationCrs( sourceA.sourceCrs(), context.transformContext() );
159
160 std::unique_ptr<QgsGeometryEngine> engine;
161 if ( !intersects.isEmpty() )
162 {
163 // use prepared geometries for faster intersection tests
164 engine.reset( QgsGeometry::createGeometryEngine( geom.constGet() ) );
165 engine->prepareGeometry();
166 }
167
168 QVector<QgsGeometry> geometriesB;
169 QgsFeature featB;
170 QgsFeatureIterator fitB = sourceB.getFeatures( request );
171 while ( fitB.nextFeature( featB ) )
172 {
173 if ( feedback->isCanceled() )
174 break;
175
176 if ( engine->intersects( featB.geometry().constGet() ) )
177 geometriesB << featB.geometry();
178 }
179
180 if ( !geometriesB.isEmpty() )
181 {
182 const QgsGeometry geomB = QgsGeometry::unaryUnion( geometriesB, parameters );
183 if ( !geomB.lastError().isEmpty() )
184 {
185 // This may happen if input geometries from a layer do not line up well (for example polygons
186 // that are nearly touching each other, but there is a very tiny overlap or gap at one of the edges).
187 // It is possible to get rid of this issue in two steps:
188 // 1. snap geometries with a small tolerance (e.g. 1cm) using QgsGeometrySnapperSingleSource
189 // 2. fix geometries (removes polygons collapsed to lines etc.) using MakeValid
190 throw QgsProcessingException( QStringLiteral( "%1\n\n%2" ).arg( QObject::tr( "GEOS geoprocessing error: unary union failed." ), geomB.lastError() ) );
191 }
192 geom = geom.difference( geomB, parameters );
193 }
194
195 if ( !geom.isNull() && !sanitizeDifferenceResult( geom, geometryType, flags ) )
196 continue;
197
198 const QgsAttributes attrsA( featA.attributes() );
199 switch ( outputAttrs )
200 {
201 case OutputA:
202 attrs = attrsA;
203 break;
204 case OutputAB:
205 for ( int i = 0; i < fieldsCountA; ++i )
206 attrs[i] = attrsA[i];
207 break;
208 case OutputBA:
209 for ( int i = 0; i < fieldsCountA; ++i )
210 attrs[i + fieldsCountB] = attrsA[i];
211 break;
212 }
213
214 QgsFeature outFeat;
215 outFeat.setGeometry( geom );
216 outFeat.setAttributes( attrs );
217 if ( !sink.addFeature( outFeat, QgsFeatureSink::FastInsert ) )
218 throw QgsProcessingException( writeFeatureError() );
219 }
220 else
221 {
222 // TODO: should we write out features that do not have geometry?
223 if ( !sink.addFeature( featA, QgsFeatureSink::FastInsert ) )
224 throw QgsProcessingException( writeFeatureError() );
225 }
226
227 ++count;
228 feedback->setProgress( count / static_cast<double>( totalCount ) * 100. );
229 }
230}
231
232
233void 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 )
234{
236 const int attrCount = fieldIndicesA.count() + fieldIndicesB.count();
237
238 QgsFeatureRequest request;
239 request.setNoAttributes();
240 request.setDestinationCrs( sourceA.sourceCrs(), context.transformContext() );
241
242 QgsFeature outFeat;
243
244 double step = sourceB.featureCount() > 0 ? 100.0 / static_cast<double>( sourceB.featureCount() ) : 1;
245 long long i = 0;
246 QgsFeatureIterator fi = sourceB.getFeatures( request );
247 feedback->setProgressText( QObject::tr( "Creating spatial index" ) );
248 const QgsSpatialIndex indexB( fi, [&]( const QgsFeature & ) -> bool {
249 i++;
250 if ( feedback->isCanceled() )
251 return false;
252
253 feedback->setProgress( static_cast<double>( i ) * step );
254
255 return true;
256 } );
257
258 if ( feedback->isCanceled() )
259 return;
260
261 if ( totalCount == 0 )
262 totalCount = 1; // avoid division by zero
263
264 feedback->setProgressText( QObject::tr( "Calculating intersection" ) );
265
266 QgsFeature featA;
267 QgsFeatureIterator fitA = sourceA.getFeatures( QgsFeatureRequest().setSubsetOfAttributes( fieldIndicesA ) );
268 while ( fitA.nextFeature( featA ) )
269 {
270 if ( feedback->isCanceled() )
271 break;
272
273 if ( !featA.hasGeometry() )
274 continue;
275
276 const QgsGeometry geom( featA.geometry() );
277 const QgsFeatureIds intersects = qgis::listToSet( indexB.intersects( geom.boundingBox() ) );
278
279 QgsFeatureRequest request;
280 request.setFilterFids( intersects );
281 request.setDestinationCrs( sourceA.sourceCrs(), context.transformContext() );
282 request.setSubsetOfAttributes( fieldIndicesB );
283
284 std::unique_ptr<QgsGeometryEngine> engine;
285 if ( !intersects.isEmpty() )
286 {
287 // use prepared geometries for faster intersection tests
288 engine.reset( QgsGeometry::createGeometryEngine( geom.constGet() ) );
289 engine->prepareGeometry();
290 }
291
292 QgsAttributes outAttributes( attrCount );
293 const QgsAttributes attrsA( featA.attributes() );
294 for ( int i = 0; i < fieldIndicesA.count(); ++i )
295 outAttributes[i] = attrsA[fieldIndicesA[i]];
296
297 QgsFeature featB;
298 QgsFeatureIterator fitB = sourceB.getFeatures( request );
299 while ( fitB.nextFeature( featB ) )
300 {
301 if ( feedback->isCanceled() )
302 break;
303
304 const QgsGeometry tmpGeom( featB.geometry() );
305 if ( !engine->intersects( tmpGeom.constGet() ) )
306 continue;
307
308 QgsGeometry intGeom = geom.intersection( tmpGeom, parameters );
309 if ( !sanitizeIntersectionResult( intGeom, geometryType ) )
310 continue;
311
312 const QgsAttributes attrsB( featB.attributes() );
313 for ( int i = 0; i < fieldIndicesB.count(); ++i )
314 outAttributes[fieldIndicesA.count() + i] = attrsB[fieldIndicesB[i]];
315
316 outFeat.setGeometry( intGeom );
317 outFeat.setAttributes( outAttributes );
318 if ( !sink.addFeature( outFeat, QgsFeatureSink::FastInsert ) )
319 throw QgsProcessingException( writeFeatureError() );
320 }
321
322 ++count;
323 feedback->setProgress( count / static_cast<double>( totalCount ) * 100. );
324 }
325}
326
327void QgsOverlayUtils::resolveOverlaps( const QgsFeatureSource &source, QgsFeatureSink &sink, QgsProcessingFeedback *feedback, const QgsGeometryParameters &parameters, SanitizeFlags flags )
328{
329 long count = 0;
330 const long totalCount = source.featureCount();
331 if ( totalCount == 0 )
332 return; // nothing to do here
333
334 QgsFeatureId newFid = -1;
335
337
338 QgsFeatureRequest requestOnlyGeoms;
339 requestOnlyGeoms.setNoAttributes();
340
341 QgsFeatureRequest requestOnlyAttrs;
343
344 QgsFeatureRequest requestOnlyIds;
346 requestOnlyIds.setNoAttributes();
347
348 // make a set of used feature IDs so that we do not try to reuse them for newly added features
349 QgsFeature f;
350 QSet<QgsFeatureId> fids;
351 QgsFeatureIterator it = source.getFeatures( requestOnlyIds );
352 while ( it.nextFeature( f ) )
353 {
354 if ( feedback->isCanceled() )
355 return;
356
357 fids.insert( f.id() );
358 }
359
360 QHash<QgsFeatureId, QgsGeometry> geometries;
361 QgsSpatialIndex index;
362 QHash<QgsFeatureId, QList<QgsFeatureId>> intersectingIds; // which features overlap a particular area
363
364 // resolve intersections
365
366 it = source.getFeatures( requestOnlyGeoms );
367 while ( it.nextFeature( f ) )
368 {
369 if ( feedback->isCanceled() )
370 return;
371
372 const QgsFeatureId fid1 = f.id();
373 QgsGeometry g1 = f.geometry();
374 std::unique_ptr<QgsGeometryEngine> g1engine;
375
376 geometries.insert( fid1, g1 );
377 index.addFeature( f );
378
379 const QgsRectangle bbox( f.geometry().boundingBox() );
380 const QList<QgsFeatureId> ids = index.intersects( bbox );
381 for ( const QgsFeatureId fid2 : ids )
382 {
383 if ( fid1 == fid2 )
384 continue;
385
386 if ( !g1engine )
387 {
388 // use prepared geometries for faster intersection tests
389 g1engine.reset( QgsGeometry::createGeometryEngine( g1.constGet() ) );
390 g1engine->prepareGeometry();
391 }
392
393 const QgsGeometry g2 = geometries.value( fid2 );
394 if ( !g1engine->intersects( g2.constGet() ) )
395 continue;
396
397 QgsGeometry geomIntersection = g1.intersection( g2, parameters );
398 if ( !sanitizeIntersectionResult( geomIntersection, geometryType ) )
399 continue;
400
401 //
402 // add intersection geometry
403 //
404
405 // figure out new fid
406 while ( fids.contains( newFid ) )
407 --newFid;
408 fids.insert( newFid );
409
410 geometries.insert( newFid, geomIntersection );
411 QgsFeature fx( newFid );
412 fx.setGeometry( geomIntersection );
413
414 index.addFeature( fx );
415
416 // figure out which feature IDs belong to this intersection. Some of the IDs can be of the newly
417 // created geometries - in such case we need to retrieve original IDs
418 QList<QgsFeatureId> lst;
419 if ( intersectingIds.contains( fid1 ) )
420 lst << intersectingIds.value( fid1 );
421 else
422 lst << fid1;
423 if ( intersectingIds.contains( fid2 ) )
424 lst << intersectingIds.value( fid2 );
425 else
426 lst << fid2;
427 intersectingIds.insert( newFid, lst );
428
429 //
430 // update f1
431 //
432
433 QgsGeometry g12 = g1.difference( g2, parameters );
434
435 index.deleteFeature( f );
436 geometries.remove( fid1 );
437
438 if ( sanitizeDifferenceResult( g12, geometryType, flags ) )
439 {
440 geometries.insert( fid1, g12 );
441
442 QgsFeature f1x( fid1 );
443 f1x.setGeometry( g12 );
444 index.addFeature( f1x );
445 }
446
447 //
448 // update f2
449 //
450
451 QgsGeometry g21 = g2.difference( g1, parameters );
452
453 QgsFeature f2old( fid2 );
454 f2old.setGeometry( g2 );
455 index.deleteFeature( f2old );
456
457 geometries.remove( fid2 );
458
459 if ( sanitizeDifferenceResult( g21, geometryType, flags ) )
460 {
461 geometries.insert( fid2, g21 );
462
463 QgsFeature f2x( fid2 );
464 f2x.setGeometry( g21 );
465 index.addFeature( f2x );
466 }
467
468 // update our temporary copy of the geometry to what is left from it
469 g1 = g12;
470 g1engine.reset();
471 }
472
473 ++count;
474 feedback->setProgress( count / static_cast<double>( totalCount ) * 100. );
475 }
476 if ( feedback->isCanceled() )
477 return;
478
479 // release some memory of structures we don't need anymore
480
481 fids.clear();
482 index = QgsSpatialIndex();
483
484 // load attributes
485
486 QHash<QgsFeatureId, QgsAttributes> attributesHash;
487 it = source.getFeatures( requestOnlyAttrs );
488 while ( it.nextFeature( f ) )
489 {
490 if ( feedback->isCanceled() )
491 return;
492
493 attributesHash.insert( f.id(), f.attributes() );
494 }
495
496 // store stuff in the sink
497
498 for ( auto i = geometries.constBegin(); i != geometries.constEnd(); ++i )
499 {
500 if ( feedback->isCanceled() )
501 return;
502
503 QgsFeature outFeature( i.key() );
504 outFeature.setGeometry( i.value() );
505
506 if ( intersectingIds.contains( i.key() ) )
507 {
508 const QList<QgsFeatureId> ids = intersectingIds.value( i.key() );
509 for ( const QgsFeatureId id : ids )
510 {
511 outFeature.setAttributes( attributesHash.value( id ) );
512 if ( !sink.addFeature( outFeature, QgsFeatureSink::FastInsert ) )
513 throw QgsProcessingException( writeFeatureError() );
514 }
515 }
516 else
517 {
518 outFeature.setAttributes( attributesHash.value( i.key() ) );
519 if ( !sink.addFeature( outFeature, QgsFeatureSink::FastInsert ) )
520 throw QgsProcessingException( writeFeatureError() );
521 }
522 }
523}
524
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition qgis.h:337
@ GeometryCollection
GeometryCollection.
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.
This class wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setFlags(Qgis::FeatureRequestFlags flags)
Sets flags that affect how features will be fetched.
QgsFeatureRequest & setFilterFids(const QgsFeatureIds &fids)
Sets the feature IDs that should 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.
QgsFeatureRequest & setInvalidGeometryCheck(Qgis::InvalidGeometryCheck check)
Sets invalid geometry checking behavior.
QgsFeatureRequest & setNoAttributes()
Set that no attributes will be fetched.
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:58
QgsAttributes attributes
Definition qgsfeature.h:67
QgsFeatureId id
Definition qgsfeature.h:66
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.
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
int count
Definition qgsfields.h:50
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.
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.)
static QgsGeometryEngine * createGeometryEngine(const QgsAbstractGeometry *geometry, double precision=0.0, Qgis::GeosCreationFlags flags=Qgis::GeosCreationFlag::SkipEmptyInteriorRings)
Creates and returns a new geometry engine representing the specified geometry using precision on a gr...
Contains information about the context in which a processing algorithm is executed.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context.
Qgis::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