20
20
21
21
// /@cond PRIVATE
22
22
23
+ // ! Makes sure that what came out from intersection of two geometries is good to be used in the output
24
+ static bool sanitizeIntersectionResult ( QgsGeometry &geom, QgsWkbTypes::GeometryType geometryType )
25
+ {
26
+ if ( geom.isNull () )
27
+ {
28
+ // 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
29
+ throw QgsProcessingException ( QStringLiteral ( " %1\n\n %2" ).arg ( QObject::tr ( " GEOS geoprocessing error: intersection failed." ), geom.lastError () ) );
30
+ }
31
+
32
+ // Intersection of geometries may give use also geometries we do not want in our results.
33
+ // For example, two square polygons touching at the corner have a point as the intersection, but no area.
34
+ // In other cases we may get a mixture of geometries in the output - we want to keep only the expected types.
35
+ if ( QgsWkbTypes::flatType ( geom.wkbType () ) == QgsWkbTypes::GeometryCollection )
36
+ {
37
+ // try to filter out irrelevant parts with different geometry type than what we want
38
+ geom.convertGeometryCollectionToSubclass ( geometryType );
39
+ if ( geom.isEmpty () )
40
+ return false ;
41
+ }
42
+
43
+ if ( QgsWkbTypes::geometryType ( geom.wkbType () ) != geometryType )
44
+ {
45
+ // we can't make use of this resulting geometry
46
+ return false ;
47
+ }
48
+
49
+ // some data providers are picky about the geometries we pass to them: we can't add single-part geometries
50
+ // when we promised multi-part geometries, so ensure we have the right type
51
+ geom.convertToMultiType ();
52
+
53
+ return true ;
54
+ }
55
+
56
+
57
+ // ! Makes sure that what came out from difference of two geometries is good to be used in the output
58
+ static bool sanitizeDifferenceResult ( QgsGeometry &geom )
59
+ {
60
+ if ( geom.isNull () )
61
+ {
62
+ // 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
63
+ throw QgsProcessingException ( QStringLiteral ( " %1\n\n %2" ).arg ( QObject::tr ( " GEOS geoprocessing error: difference failed." ), geom.lastError () ) );
64
+ }
65
+
66
+ // if geomB covers the whole source geometry, we get an empty geometry collection
67
+ if ( geom.isEmpty () )
68
+ return false ;
69
+
70
+ // some data providers are picky about the geometries we pass to them: we can't add single-part geometries
71
+ // when we promised multi-part geometries, so ensure we have the right type
72
+ geom.convertToMultiType ();
73
+
74
+ return true ;
75
+ }
76
+
77
+
23
78
void QgsOverlayUtils::difference ( const QgsFeatureSource &sourceA, const QgsFeatureSource &sourceB, QgsFeatureSink &sink, QgsProcessingContext &context, QgsProcessingFeedback *feedback, int &count, int totalCount, QgsOverlayUtils::DifferenceOutput outputAttrs )
24
79
{
25
80
QgsFeatureRequest requestB;
@@ -83,8 +138,7 @@ void QgsOverlayUtils::difference( const QgsFeatureSource &sourceA, const QgsFeat
83
138
geom = geom.difference ( geomB );
84
139
}
85
140
86
- // if geomB covers the whole source geometry, we get an empty geometry collection
87
- if ( geom.isEmpty () )
141
+ if ( !sanitizeDifferenceResult ( geom ) )
88
142
continue ;
89
143
90
144
const QgsAttributes attrsA ( featA.attributes () );
@@ -104,7 +158,6 @@ void QgsOverlayUtils::difference( const QgsFeatureSource &sourceA, const QgsFeat
104
158
}
105
159
106
160
QgsFeature outFeat;
107
- geom.convertToMultiType ();
108
161
outFeat.setGeometry ( geom );
109
162
outFeat.setAttributes ( attrs );
110
163
sink.addFeature ( outFeat, QgsFeatureSink::FastInsert );
@@ -179,35 +232,13 @@ void QgsOverlayUtils::intersection( const QgsFeatureSource &sourceA, const QgsFe
179
232
continue ;
180
233
181
234
QgsGeometry intGeom = geom.intersection ( tmpGeom );
182
-
183
- if ( intGeom.isNull () )
184
- {
185
- // 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
186
- throw QgsProcessingException ( QStringLiteral ( " %1\n\n %2" ).arg ( QObject::tr ( " GEOS geoprocessing error: intersection failed." ), intGeom.lastError () ) );
187
- }
188
-
189
- // Intersection of geometries may give use also geometries we do not want in our results.
190
- // For example, two square polygons touching at the corner have a point as the intersection, but no area.
191
- // In other cases we may get a mixture of geometries in the output - we want to keep only the expected types.
192
- if ( QgsWkbTypes::flatType ( intGeom.wkbType () ) == QgsWkbTypes::GeometryCollection )
193
- {
194
- // try to filter out irrelevant parts with different geometry type than what we want
195
- intGeom.convertGeometryCollectionToSubclass ( geometryType );
196
- if ( intGeom.isEmpty () )
197
- continue ;
198
- }
199
-
200
- if ( QgsWkbTypes::geometryType ( intGeom.wkbType () ) != geometryType )
201
- {
202
- // we can't make use of this resulting geometry
235
+ if ( !sanitizeIntersectionResult ( intGeom, geometryType ) )
203
236
continue ;
204
- }
205
237
206
238
const QgsAttributes attrsB ( featB.attributes () );
207
239
for ( int i = 0 ; i < fieldIndicesB.count (); ++i )
208
240
outAttributes[fieldIndicesA.count () + i] = attrsB[fieldIndicesB[i]];
209
241
210
- intGeom.convertToMultiType ();
211
242
outFeat.setGeometry ( intGeom );
212
243
outFeat.setAttributes ( outAttributes );
213
244
sink.addFeature ( outFeat, QgsFeatureSink::FastInsert );
@@ -218,4 +249,189 @@ void QgsOverlayUtils::intersection( const QgsFeatureSource &sourceA, const QgsFe
218
249
}
219
250
}
220
251
252
+ void QgsOverlayUtils::resolveOverlaps ( const QgsFeatureSource &source, QgsFeatureSink &sink, QgsProcessingFeedback *feedback )
253
+ {
254
+ int count = 0 ;
255
+ int totalCount = source.featureCount ();
256
+ if ( totalCount == 0 )
257
+ return ; // nothing to do here
258
+
259
+ QgsFeatureId newFid = -1 ;
260
+
261
+ QgsWkbTypes::GeometryType geometryType = QgsWkbTypes::geometryType ( QgsWkbTypes::multiType ( source.wkbType () ) );
262
+
263
+ QgsFeatureRequest requestOnlyGeoms;
264
+ requestOnlyGeoms.setSubsetOfAttributes ( QgsAttributeList () );
265
+
266
+ QgsFeatureRequest requestOnlyAttrs;
267
+ requestOnlyAttrs.setFlags ( QgsFeatureRequest::NoGeometry );
268
+
269
+ QgsFeatureRequest requestOnlyIds;
270
+ requestOnlyIds.setFlags ( QgsFeatureRequest::NoGeometry );
271
+ requestOnlyIds.setSubsetOfAttributes ( QgsAttributeList () );
272
+
273
+ // make a set of used feature IDs so they we do not try to reuse them for newly added features
274
+ QgsFeature f;
275
+ QSet<QgsFeatureId> fids;
276
+ QgsFeatureIterator it = source.getFeatures ( requestOnlyIds );
277
+ while ( it.nextFeature ( f ) )
278
+ {
279
+ if ( feedback->isCanceled () )
280
+ return ;
281
+
282
+ fids.insert ( f.id () );
283
+ }
284
+
285
+ QHash<QgsFeatureId, QgsGeometry> geometries;
286
+ QgsSpatialIndex index;
287
+ QHash<QgsFeatureId, QList<QgsFeatureId> > intersectingIds; // which features overlap a particular area
288
+
289
+ // resolve intersections
290
+
291
+ it = source.getFeatures ( requestOnlyGeoms );
292
+ while ( it.nextFeature ( f ) )
293
+ {
294
+ if ( feedback->isCanceled () )
295
+ return ;
296
+
297
+ QgsFeatureId fid1 = f.id ();
298
+ QgsGeometry g1 = f.geometry ();
299
+
300
+ geometries.insert ( fid1, g1 );
301
+ index.insertFeature ( f );
302
+
303
+ QgsRectangle bbox ( f.geometry ().boundingBox () );
304
+ const QList<QgsFeatureId> ids = index.intersects ( bbox );
305
+ for ( QgsFeatureId fid2 : ids )
306
+ {
307
+ if ( fid1 == fid2 )
308
+ continue ;
309
+
310
+ QgsGeometry g2 = geometries.value ( fid2 );
311
+ if ( !g1.intersects ( g2 ) )
312
+ continue ;
313
+
314
+ QgsGeometry geomIntersection = g1.intersection ( g2 );
315
+ if ( !sanitizeIntersectionResult ( geomIntersection, geometryType ) )
316
+ continue ;
317
+
318
+ //
319
+ // add intersection geometry
320
+ //
321
+
322
+ // figure out new fid
323
+ while ( fids.contains ( newFid ) )
324
+ --newFid;
325
+ fids.insert ( newFid );
326
+
327
+ geometries.insert ( newFid, geomIntersection );
328
+ QgsFeature fx ( newFid );
329
+ fx.setGeometry ( geomIntersection );
330
+
331
+ index.insertFeature ( fx );
332
+
333
+ // figure out which feature IDs belong to this intersection. Some of the IDs can be of the newly
334
+ // created geometries - in such case we need to retrieve original IDs
335
+ QList<QgsFeatureId> lst;
336
+ if ( intersectingIds.contains ( fid1 ) )
337
+ lst << intersectingIds.value ( fid1 );
338
+ else
339
+ lst << fid1;
340
+ if ( intersectingIds.contains ( fid2 ) )
341
+ lst << intersectingIds.value ( fid2 );
342
+ else
343
+ lst << fid2;
344
+ intersectingIds.insert ( newFid, lst );
345
+
346
+ //
347
+ // update f1
348
+ //
349
+
350
+ QgsGeometry g12 = g1.difference ( g2 );
351
+
352
+ index.deleteFeature ( f );
353
+ geometries.remove ( fid1 );
354
+
355
+ if ( sanitizeDifferenceResult ( g12 ) )
356
+ {
357
+ geometries.insert ( fid1, g12 );
358
+
359
+ QgsFeature f1x ( fid1 );
360
+ f1x.setGeometry ( g12 );
361
+ index.insertFeature ( f1x );
362
+ }
363
+
364
+ //
365
+ // update f2
366
+ //
367
+
368
+ QgsGeometry g21 = g2.difference ( g1 );
369
+
370
+ QgsFeature f2old ( fid2 );
371
+ f2old.setGeometry ( g2 );
372
+ index.deleteFeature ( f2old );
373
+
374
+ geometries.remove ( fid2 );
375
+
376
+ if ( sanitizeDifferenceResult ( g21 ) )
377
+ {
378
+ geometries.insert ( fid2, g21 );
379
+
380
+ QgsFeature f2x ( fid2 );
381
+ f2x.setGeometry ( g21 );
382
+ index.insertFeature ( f2x );
383
+ }
384
+
385
+ // update our temporary copy of the geometry to what is left from it
386
+ g1 = g12;
387
+ }
388
+
389
+ ++count;
390
+ feedback->setProgress ( count / ( double ) totalCount * 100 . );
391
+ }
392
+
393
+ // release some memory of structures we don't need anymore
394
+
395
+ fids.clear ();
396
+ index = QgsSpatialIndex ();
397
+
398
+ // load attributes
399
+
400
+ QHash<QgsFeatureId, QgsAttributes> attributesHash;
401
+ it = source.getFeatures ( requestOnlyAttrs );
402
+ while ( it.nextFeature ( f ) )
403
+ {
404
+ if ( feedback->isCanceled () )
405
+ return ;
406
+
407
+ attributesHash.insert ( f.id (), f.attributes () );
408
+ }
409
+
410
+ // store stuff in the sink
411
+
412
+ for ( auto i = geometries.constBegin (); i != geometries.constEnd (); ++i )
413
+ {
414
+ if ( feedback->isCanceled () )
415
+ return ;
416
+
417
+ QgsFeature outFeature ( i.key () );
418
+ outFeature.setGeometry ( i.value () );
419
+
420
+ if ( intersectingIds.contains ( i.key () ) )
421
+ {
422
+ const QList<QgsFeatureId> ids = intersectingIds.value ( i.key () );
423
+ for ( QgsFeatureId id : ids )
424
+ {
425
+ outFeature.setAttributes ( attributesHash.value ( id ) );
426
+ sink.addFeature ( outFeature, QgsFeatureSink::FastInsert );
427
+ }
428
+ }
429
+ else
430
+ {
431
+ outFeature.setAttributes ( attributesHash.value ( i.key () ) );
432
+ sink.addFeature ( outFeature, QgsFeatureSink::FastInsert );
433
+ }
434
+ }
435
+ }
436
+
221
437
// /@endcond PRIVATE
0 commit comments