2020
2121// /@cond PRIVATE
2222
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+
2378void QgsOverlayUtils::difference ( const QgsFeatureSource &sourceA, const QgsFeatureSource &sourceB, QgsFeatureSink &sink, QgsProcessingContext &context, QgsProcessingFeedback *feedback, int &count, int totalCount, QgsOverlayUtils::DifferenceOutput outputAttrs )
2479{
2580 QgsFeatureRequest requestB;
@@ -83,8 +138,7 @@ void QgsOverlayUtils::difference( const QgsFeatureSource &sourceA, const QgsFeat
83138 geom = geom.difference ( geomB );
84139 }
85140
86- // if geomB covers the whole source geometry, we get an empty geometry collection
87- if ( geom.isEmpty () )
141+ if ( !sanitizeDifferenceResult ( geom ) )
88142 continue ;
89143
90144 const QgsAttributes attrsA ( featA.attributes () );
@@ -104,7 +158,6 @@ void QgsOverlayUtils::difference( const QgsFeatureSource &sourceA, const QgsFeat
104158 }
105159
106160 QgsFeature outFeat;
107- geom.convertToMultiType ();
108161 outFeat.setGeometry ( geom );
109162 outFeat.setAttributes ( attrs );
110163 sink.addFeature ( outFeat, QgsFeatureSink::FastInsert );
@@ -179,35 +232,13 @@ void QgsOverlayUtils::intersection( const QgsFeatureSource &sourceA, const QgsFe
179232 continue ;
180233
181234 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 ) )
203236 continue ;
204- }
205237
206238 const QgsAttributes attrsB ( featB.attributes () );
207239 for ( int i = 0 ; i < fieldIndicesB.count (); ++i )
208240 outAttributes[fieldIndicesA.count () + i] = attrsB[fieldIndicesB[i]];
209241
210- intGeom.convertToMultiType ();
211242 outFeat.setGeometry ( intGeom );
212243 outFeat.setAttributes ( outAttributes );
213244 sink.addFeature ( outFeat, QgsFeatureSink::FastInsert );
@@ -218,4 +249,189 @@ void QgsOverlayUtils::intersection( const QgsFeatureSource &sourceA, const QgsFe
218249 }
219250}
220251
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+
221437// /@endcond PRIVATE
0 commit comments