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