QGIS API Documentation 3.99.0-Master (09f76ad7019)
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 "qgsfeature.h"
19#include "qgsfeaturerequest.h"
20#include "qgsfeaturesource.h"
21#include "qgsgeometryengine.h"
23#include "qgsspatialindex.h"
24
25#include <QString>
26
27using namespace Qt::StringLiterals;
28
30
31bool QgsOverlayUtils::sanitizeIntersectionResult( QgsGeometry &geom, Qgis::GeometryType geometryType, SanitizeFlags flags )
32{
33 if ( geom.isNull() )
34 {
35 // 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
36 throw QgsProcessingException( u"%1\n\n%2"_s.arg( QObject::tr( "GEOS geoprocessing error: intersection failed." ), geom.lastError() ) );
37 }
38
39 // Intersection of geometries may give use also geometries we do not want in our results.
40 // For example, two square polygons touching at the corner have a point as the intersection, but no area.
41 // In other cases we may get a mixture of geometries in the output - we want to keep only the expected types.
43 {
44 // try to filter out irrelevant parts with different geometry type than what we want
45 geom.convertGeometryCollectionToSubclass( geometryType );
46 if ( geom.isEmpty() )
47 return false;
48 }
49
50 if ( QgsWkbTypes::geometryType( geom.wkbType() ) != geometryType )
51 {
52 // we can't make use of this resulting geometry
53 return false;
54 }
55
56 if ( geometryType != Qgis::GeometryType::Point
57 || !( flags & SanitizeFlag::DontPromotePointGeometryToMultiPoint ) )
58 {
59 // some data providers are picky about the geometries we pass to them: we can't add single-part geometries
60 // when we promised multi-part geometries, so ensure we have the right type
61 geom.convertToMultiType();
62 }
63
64 return true;
65}
66
67
69static bool sanitizeDifferenceResult( QgsGeometry &geom, Qgis::GeometryType geometryType, QgsOverlayUtils::SanitizeFlags flags )
70{
71 if ( geom.isNull() )
72 {
73 // 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
74 throw QgsProcessingException( u"%1\n\n%2"_s.arg( QObject::tr( "GEOS geoprocessing error: difference failed." ), geom.lastError() ) );
75 }
76
77 //fix geometry collections
79 {
80 // try to filter out irrelevant parts with different geometry type than what we want
81 geom.convertGeometryCollectionToSubclass( geometryType );
82 }
83
84
85 // if geomB covers the whole source geometry, we get an empty geometry collection
86 if ( geom.isEmpty() )
87 return false;
88
89 if ( geometryType != Qgis::GeometryType::Point
90 || !( flags & QgsOverlayUtils::SanitizeFlag::DontPromotePointGeometryToMultiPoint ) )
91 {
92 // some data providers are picky about the geometries we pass to them: we can't add single-part geometries
93 // when we promised multi-part geometries, so ensure we have the right type
94 geom.convertToMultiType();
95 }
96
97 return true;
98}
99
100
101static QString writeFeatureError()
102{
103 return QObject::tr( "Could not write feature" );
104}
105
106void 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 )
107{
109 QgsFeatureRequest requestB;
110 requestB.setNoAttributes();
111 if ( outputAttrs != OutputBA )
112 requestB.setDestinationCrs( sourceA.sourceCrs(), context.transformContext() );
113
114 double step = sourceB.featureCount() > 0 ? 100.0 / static_cast<double>( sourceB.featureCount() ) : 1;
115 long long i = 0;
116 QgsFeatureIterator fi = sourceB.getFeatures( requestB );
117
118 feedback->setProgressText( QObject::tr( "Creating spatial index" ) );
119 const QgsSpatialIndex indexB( fi, [&]( const QgsFeature & ) -> bool {
120 i++;
121 if ( feedback->isCanceled() )
122 return false;
123
124 feedback->setProgress( static_cast<double>( i ) * step );
125
126 return true;
127 } );
128
129 if ( feedback->isCanceled() )
130 return;
131
132 const int fieldsCountA = sourceA.fields().count();
133 const int fieldsCountB = sourceB.fields().count();
134 QgsAttributes attrs;
135 attrs.resize( outputAttrs == OutputA ? fieldsCountA : ( fieldsCountA + fieldsCountB ) );
136
137 if ( totalCount == 0 )
138 totalCount = 1; // avoid division by zero
139
140 feedback->setProgressText( QObject::tr( "Calculating difference" ) );
141
142 QgsFeature featA;
143 QgsFeatureRequest requestA;
144 requestA.setInvalidGeometryCheck( context.invalidGeometryCheck() );
145 if ( outputAttrs == OutputBA )
146 requestA.setDestinationCrs( sourceB.sourceCrs(), context.transformContext() );
147 QgsFeatureIterator fitA = sourceA.getFeatures( requestA );
148 while ( fitA.nextFeature( featA ) )
149 {
150 if ( feedback->isCanceled() )
151 break;
152
153 if ( featA.hasGeometry() )
154 {
155 QgsGeometry geom( featA.geometry() );
156 const QgsFeatureIds intersects = qgis::listToSet( indexB.intersects( geom.boundingBox() ) );
157
158 QgsFeatureRequest request;
159 request.setFilterFids( intersects );
160 request.setNoAttributes();
161 if ( outputAttrs != OutputBA )
162 request.setDestinationCrs( sourceA.sourceCrs(), context.transformContext() );
163
164 std::unique_ptr<QgsGeometryEngine> engine;
165
166 QVector<QgsGeometry> geometriesB;
167 QgsFeature featB;
168 QgsFeatureIterator fitB = sourceB.getFeatures( request );
169 while ( fitB.nextFeature( featB ) )
170 {
171 if ( feedback->isCanceled() )
172 break;
173
174 if ( !engine )
175 {
176 // use prepared geometries for faster intersection tests
177 engine.reset( QgsGeometry::createGeometryEngine( geom.constGet() ) );
178 engine->prepareGeometry();
179 }
180 if ( engine->intersects( featB.geometry().constGet() ) )
181 geometriesB << featB.geometry();
182 }
183
184 if ( !geometriesB.isEmpty() )
185 {
186 const QgsGeometry geomB = QgsGeometry::unaryUnion( geometriesB, parameters );
187 if ( !geomB.lastError().isEmpty() )
188 {
189 // This may happen if input geometries from a layer do not line up well (for example polygons
190 // that are nearly touching each other, but there is a very tiny overlap or gap at one of the edges).
191 // It is possible to get rid of this issue in two steps:
192 // 1. snap geometries with a small tolerance (e.g. 1cm) using QgsGeometrySnapperSingleSource
193 // 2. fix geometries (removes polygons collapsed to lines etc.) using MakeValid
194 throw QgsProcessingException( u"%1\n\n%2"_s.arg( QObject::tr( "GEOS geoprocessing error: unary union failed." ), geomB.lastError() ) );
195 }
196 geom = geom.difference( geomB, parameters );
197 }
198
199 if ( !geom.isNull() && !sanitizeDifferenceResult( geom, geometryType, flags ) )
200 continue;
201
202 const QgsAttributes attrsA( featA.attributes() );
203 switch ( outputAttrs )
204 {
205 case OutputA:
206 attrs = attrsA;
207 break;
208 case OutputAB:
209 for ( int i = 0; i < fieldsCountA; ++i )
210 attrs[i] = attrsA[i];
211 break;
212 case OutputBA:
213 for ( int i = 0; i < fieldsCountA; ++i )
214 attrs[i + fieldsCountB] = attrsA[i];
215 break;
216 }
217
218 QgsFeature outFeat;
219 outFeat.setGeometry( geom );
220 outFeat.setAttributes( attrs );
221 if ( !sink.addFeature( outFeat, QgsFeatureSink::FastInsert ) )
222 throw QgsProcessingException( writeFeatureError() );
223 }
224 else
225 {
226 // TODO: should we write out features that do not have geometry?
227 if ( !sink.addFeature( featA, QgsFeatureSink::FastInsert ) )
228 throw QgsProcessingException( writeFeatureError() );
229 }
230
231 ++count;
232 feedback->setProgress( count / static_cast<double>( totalCount ) * 100. );
233 }
234}
235
236
237void 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 )
238{
240 const int attrCount = fieldIndicesA.count() + fieldIndicesB.count();
241
242 QgsFeatureRequest request;
243 request.setNoAttributes();
244 request.setDestinationCrs( sourceA.sourceCrs(), context.transformContext() );
245
246 QgsFeature outFeat;
247
248 double step = sourceB.featureCount() > 0 ? 100.0 / static_cast<double>( sourceB.featureCount() ) : 1;
249 long long i = 0;
250 QgsFeatureIterator fi = sourceB.getFeatures( request );
251 feedback->setProgressText( QObject::tr( "Creating spatial index" ) );
252 const QgsSpatialIndex indexB( fi, [&]( const QgsFeature & ) -> bool {
253 i++;
254 if ( feedback->isCanceled() )
255 return false;
256
257 feedback->setProgress( static_cast<double>( i ) * step );
258
259 return true;
260 } );
261
262 if ( feedback->isCanceled() )
263 return;
264
265 if ( totalCount == 0 )
266 totalCount = 1; // avoid division by zero
267
268 feedback->setProgressText( QObject::tr( "Calculating intersection" ) );
269
270 QgsFeature featA;
271 QgsFeatureIterator fitA = sourceA.getFeatures( QgsFeatureRequest().setSubsetOfAttributes( fieldIndicesA ) );
272 while ( fitA.nextFeature( featA ) )
273 {
274 if ( feedback->isCanceled() )
275 break;
276
277 if ( !featA.hasGeometry() )
278 continue;
279
280 const QgsGeometry geom( featA.geometry() );
281 const QgsFeatureIds intersects = qgis::listToSet( indexB.intersects( geom.boundingBox() ) );
282
283 QgsFeatureRequest request;
284 request.setFilterFids( intersects );
285 request.setDestinationCrs( sourceA.sourceCrs(), context.transformContext() );
286 request.setSubsetOfAttributes( fieldIndicesB );
287
288 std::unique_ptr<QgsGeometryEngine> engine;
289 if ( !intersects.isEmpty() )
290 {
291 // use prepared geometries for faster intersection tests
292 engine.reset( QgsGeometry::createGeometryEngine( geom.constGet() ) );
293 engine->prepareGeometry();
294 }
295
296 QgsAttributes outAttributes( attrCount );
297 const QgsAttributes attrsA( featA.attributes() );
298 for ( int i = 0; i < fieldIndicesA.count(); ++i )
299 outAttributes[i] = attrsA[fieldIndicesA[i]];
300
301 QgsFeature featB;
302 QgsFeatureIterator fitB = sourceB.getFeatures( request );
303 while ( fitB.nextFeature( featB ) )
304 {
305 if ( feedback->isCanceled() )
306 break;
307
308 const QgsGeometry tmpGeom( featB.geometry() );
309 if ( !engine->intersects( tmpGeom.constGet() ) )
310 continue;
311
312 QgsGeometry intGeom = geom.intersection( tmpGeom, parameters );
313 if ( !sanitizeIntersectionResult( intGeom, geometryType ) )
314 continue;
315
316 const QgsAttributes attrsB( featB.attributes() );
317 for ( int i = 0; i < fieldIndicesB.count(); ++i )
318 outAttributes[fieldIndicesA.count() + i] = attrsB[fieldIndicesB[i]];
319
320 outFeat.setGeometry( intGeom );
321 outFeat.setAttributes( outAttributes );
322 if ( !sink.addFeature( outFeat, QgsFeatureSink::FastInsert ) )
323 throw QgsProcessingException( writeFeatureError() );
324 }
325
326 ++count;
327 feedback->setProgress( count / static_cast<double>( totalCount ) * 100. );
328 }
329}
330
331void QgsOverlayUtils::resolveOverlaps( const QgsFeatureSource &source, QgsFeatureSink &sink, QgsProcessingFeedback *feedback, const QgsGeometryParameters &parameters, SanitizeFlags flags )
332{
333 long count = 0;
334 const long totalCount = source.featureCount();
335 if ( totalCount == 0 )
336 return; // nothing to do here
337
338 QgsFeatureId newFid = -1;
339
341
342 QgsFeatureRequest requestOnlyGeoms;
343 requestOnlyGeoms.setNoAttributes();
344
345 QgsFeatureRequest requestOnlyAttrs;
347
348 QgsFeatureRequest requestOnlyIds;
350 requestOnlyIds.setNoAttributes();
351
352 // make a set of used feature IDs so that we do not try to reuse them for newly added features
353 QgsFeature f;
354 QSet<QgsFeatureId> fids;
355 QgsFeatureIterator it = source.getFeatures( requestOnlyIds );
356 while ( it.nextFeature( f ) )
357 {
358 if ( feedback->isCanceled() )
359 return;
360
361 fids.insert( f.id() );
362 }
363
364 QHash<QgsFeatureId, QgsGeometry> geometries;
365 QgsSpatialIndex index;
366 QHash<QgsFeatureId, QList<QgsFeatureId>> intersectingIds; // which features overlap a particular area
367
368 // resolve intersections
369
370 it = source.getFeatures( requestOnlyGeoms );
371 while ( it.nextFeature( f ) )
372 {
373 if ( feedback->isCanceled() )
374 return;
375
376 const QgsFeatureId fid1 = f.id();
377 QgsGeometry g1 = f.geometry();
378 std::unique_ptr<QgsGeometryEngine> g1engine;
379
380 geometries.insert( fid1, g1 );
381 index.addFeature( f );
382
383 const QgsRectangle bbox( f.geometry().boundingBox() );
384 const QList<QgsFeatureId> ids = index.intersects( bbox );
385 for ( const QgsFeatureId fid2 : ids )
386 {
387 if ( fid1 == fid2 )
388 continue;
389
390 if ( !g1engine )
391 {
392 // use prepared geometries for faster intersection tests
393 g1engine.reset( QgsGeometry::createGeometryEngine( g1.constGet() ) );
394 g1engine->prepareGeometry();
395 }
396
397 const QgsGeometry g2 = geometries.value( fid2 );
398 if ( !g1engine->intersects( g2.constGet() ) )
399 continue;
400
401 QgsGeometry geomIntersection = g1.intersection( g2, parameters );
402 if ( !sanitizeIntersectionResult( geomIntersection, geometryType ) )
403 continue;
404
405 //
406 // add intersection geometry
407 //
408
409 // figure out new fid
410 while ( fids.contains( newFid ) )
411 --newFid;
412 fids.insert( newFid );
413
414 geometries.insert( newFid, geomIntersection );
415 QgsFeature fx( newFid );
416 fx.setGeometry( geomIntersection );
417
418 index.addFeature( fx );
419
420 // figure out which feature IDs belong to this intersection. Some of the IDs can be of the newly
421 // created geometries - in such case we need to retrieve original IDs
422 QList<QgsFeatureId> lst;
423 if ( intersectingIds.contains( fid1 ) )
424 lst << intersectingIds.value( fid1 );
425 else
426 lst << fid1;
427 if ( intersectingIds.contains( fid2 ) )
428 lst << intersectingIds.value( fid2 );
429 else
430 lst << fid2;
431 intersectingIds.insert( newFid, lst );
432
433 //
434 // update f1
435 //
436
437 QgsGeometry g12 = g1.difference( g2, parameters );
438
439 index.deleteFeature( f );
440 geometries.remove( fid1 );
441
442 if ( sanitizeDifferenceResult( g12, geometryType, flags ) )
443 {
444 geometries.insert( fid1, g12 );
445
446 QgsFeature f1x( fid1 );
447 f1x.setGeometry( g12 );
448 index.addFeature( f1x );
449 }
450
451 //
452 // update f2
453 //
454
455 QgsGeometry g21 = g2.difference( g1, parameters );
456
457 QgsFeature f2old( fid2 );
458 f2old.setGeometry( g2 );
459 index.deleteFeature( f2old );
460
461 geometries.remove( fid2 );
462
463 if ( sanitizeDifferenceResult( g21, geometryType, flags ) )
464 {
465 geometries.insert( fid2, g21 );
466
467 QgsFeature f2x( fid2 );
468 f2x.setGeometry( g21 );
469 index.addFeature( f2x );
470 }
471
472 // update our temporary copy of the geometry to what is left from it
473 g1 = g12;
474 g1engine.reset();
475 }
476
477 ++count;
478 feedback->setProgress( count / static_cast<double>( totalCount ) * 100. );
479 }
480 if ( feedback->isCanceled() )
481 return;
482
483 // release some memory of structures we don't need anymore
484
485 fids.clear();
486 index = QgsSpatialIndex();
487
488 // load attributes
489
490 QHash<QgsFeatureId, QgsAttributes> attributesHash;
491 it = source.getFeatures( requestOnlyAttrs );
492 while ( it.nextFeature( f ) )
493 {
494 if ( feedback->isCanceled() )
495 return;
496
497 attributesHash.insert( f.id(), f.attributes() );
498 }
499
500 // store stuff in the sink
501
502 for ( auto i = geometries.constBegin(); i != geometries.constEnd(); ++i )
503 {
504 if ( feedback->isCanceled() )
505 return;
506
507 QgsFeature outFeature( i.key() );
508 outFeature.setGeometry( i.value() );
509
510 if ( intersectingIds.contains( i.key() ) )
511 {
512 const QList<QgsFeatureId> ids = intersectingIds.value( i.key() );
513 for ( const QgsFeatureId id : ids )
514 {
515 outFeature.setAttributes( attributesHash.value( id ) );
516 if ( !sink.addFeature( outFeature, QgsFeatureSink::FastInsert ) )
517 throw QgsProcessingException( writeFeatureError() );
518 }
519 }
520 else
521 {
522 outFeature.setAttributes( attributesHash.value( i.key() ) );
523 if ( !sink.addFeature( outFeature, QgsFeatureSink::FastInsert ) )
524 throw QgsProcessingException( writeFeatureError() );
525 }
526 }
527}
528
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
Definition qgis.h:2254
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition qgis.h:365
@ Point
Points.
Definition qgis.h:366
@ GeometryCollection
GeometryCollection.
Definition qgis.h:289
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).
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:60
QgsAttributes attributes
Definition qgsfeature.h:69
QgsFeatureId id
Definition qgsfeature.h:68
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.
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
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