1 /***************************************************************************
2 qgsgeometrygapcheck.cpp
3 ---------------------
4 begin : September 2015
5 copyright : (C) 2014 by Sandro Mani / Sourcepole AG
6 email : smani at sourcepole dot ch
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 "qgsgeometrycheckcontext.h"
17 #include "qgsgeometryengine.h"
18 #include "qgsgeometrygapcheck.h"
19 #include "qgsgeometrycollection.h"
20 #include "qgsfeaturepool.h"
21 #include "qgsvectorlayer.h"
22 #include "qgsvectorlayerutils.h"
23 #include "qgsfeedback.h"
24 #include "qgsapplication.h"
25 #include "qgsproject.h"
26 #include "qgsexpressioncontextutils.h"
27 #include "qgspolygon.h"
28 #include "qgscurve.h"
29 #include "qgssnappingutils.h"
30
31 #include "geos_c.h"
32
QgsGeometryGapCheck(const QgsGeometryCheckContext * context,const QVariantMap & configuration)33 QgsGeometryGapCheck::QgsGeometryGapCheck( const QgsGeometryCheckContext *context, const QVariantMap &configuration )
34 : QgsGeometryCheck( context, configuration )
35 , mGapThresholdMapUnits( configuration.value( QStringLiteral( "gapThreshold" ) ).toDouble() )
36 {
37 }
38
prepare(const QgsGeometryCheckContext * context,const QVariantMap & configuration)39 void QgsGeometryGapCheck::prepare( const QgsGeometryCheckContext *context, const QVariantMap &configuration )
40 {
41 if ( configuration.value( QStringLiteral( "allowedGapsEnabled" ) ).toBool() )
42 {
43 QgsVectorLayer *layer = context->project()->mapLayer<QgsVectorLayer *>( configuration.value( "allowedGapsLayer" ).toString() );
44 if ( layer )
45 {
46 mAllowedGapsLayer = layer;
47 mAllowedGapsSource = qgis::make_unique<QgsVectorLayerFeatureSource>( layer );
48
49 mAllowedGapsBuffer = configuration.value( QStringLiteral( "allowedGapsBuffer" ) ).toDouble();
50 }
51 }
52 else
53 {
54 mAllowedGapsSource.reset();
55 }
56 }
57
collectErrors(const QMap<QString,QgsFeaturePool * > & featurePools,QList<QgsGeometryCheckError * > & errors,QStringList & messages,QgsFeedback * feedback,const LayerFeatureIds & ids) const58 void QgsGeometryGapCheck::collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids ) const
59 {
60 if ( feedback )
61 feedback->setProgress( feedback->progress() + 1.0 );
62
63 std::unique_ptr<QgsAbstractGeometry> allowedGapsGeom;
64 std::unique_ptr<QgsGeometryEngine> allowedGapsGeomEngine;
65
66 if ( mAllowedGapsSource )
67 {
68 QVector<QgsGeometry> allowedGaps;
69 QgsFeatureRequest request;
70 request.setSubsetOfAttributes( QgsAttributeList() );
71 QgsFeatureIterator iterator = mAllowedGapsSource->getFeatures( request );
72 QgsFeature feature;
73
74 while ( iterator.nextFeature( feature ) )
75 {
76 QgsGeometry geom = feature.geometry();
77 QgsGeometry gg = geom.buffer( mAllowedGapsBuffer, 20 );
78 allowedGaps.append( gg );
79 }
80
81 std::unique_ptr< QgsGeometryEngine > allowedGapsEngine = QgsGeometryCheckerUtils::createGeomEngine( nullptr, mContext->tolerance );
82
83 // Create union of allowed gaps
84 QString errMsg;
85 allowedGapsGeom.reset( allowedGapsEngine->combine( allowedGaps, &errMsg ) );
86 allowedGapsGeomEngine = QgsGeometryCheckerUtils::createGeomEngine( allowedGapsGeom.get(), mContext->tolerance );
87 allowedGapsGeomEngine->prepareGeometry();
88 }
89
90 QVector<QgsGeometry> geomList;
91 QMap<QString, QgsFeatureIds> featureIds = ids.isEmpty() ? allLayerFeatureIds( featurePools ) : ids.toMap();
92 const QgsGeometryCheckerUtils::LayerFeatures layerFeatures( featurePools, featureIds, compatibleGeometryTypes(), nullptr, mContext, true );
93 for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeature : layerFeatures )
94 {
95 geomList.append( layerFeature.geometry() );
96
97 if ( feedback && feedback->isCanceled() )
98 {
99 geomList.clear();
100 break;
101 }
102 }
103
104 if ( geomList.isEmpty() )
105 {
106 return;
107 }
108
109 std::unique_ptr< QgsGeometryEngine > geomEngine = QgsGeometryCheckerUtils::createGeomEngine( nullptr, mContext->tolerance );
110
111 // Create union of geometry
112 QString errMsg;
113 std::unique_ptr<QgsAbstractGeometry> unionGeom( geomEngine->combine( geomList, &errMsg ) );
114 if ( !unionGeom )
115 {
116 messages.append( tr( "Gap check: %1" ).arg( errMsg ) );
117 return;
118 }
119
120 // Get envelope of union
121 geomEngine = QgsGeometryCheckerUtils::createGeomEngine( unionGeom.get(), mContext->tolerance );
122 geomEngine->prepareGeometry();
123 std::unique_ptr<QgsAbstractGeometry> envelope( geomEngine->envelope( &errMsg ) );
124 if ( !envelope )
125 {
126 messages.append( tr( "Gap check: %1" ).arg( errMsg ) );
127 return;
128 }
129
130 // Buffer envelope
131 geomEngine = QgsGeometryCheckerUtils::createGeomEngine( envelope.get(), mContext->tolerance );
132 geomEngine->prepareGeometry();
133 QgsAbstractGeometry *bufEnvelope = geomEngine->buffer( 2, 0, GEOSBUF_CAP_SQUARE, GEOSBUF_JOIN_MITRE, 4. ); //#spellok //#spellok
134 envelope.reset( bufEnvelope );
135
136 // Compute difference between envelope and union to obtain gap polygons
137 geomEngine = QgsGeometryCheckerUtils::createGeomEngine( envelope.get(), mContext->tolerance );
138 geomEngine->prepareGeometry();
139 std::unique_ptr<QgsAbstractGeometry> diffGeom( geomEngine->difference( unionGeom.get(), &errMsg ) );
140 if ( !diffGeom )
141 {
142 messages.append( tr( "Gap check: %1" ).arg( errMsg ) );
143 return;
144 }
145
146 // For each gap polygon which does not lie on the boundary, get neighboring polygons and add error
147 QgsGeometryPartIterator parts = diffGeom->parts();
148 while ( parts.hasNext() )
149 {
150 const QgsAbstractGeometry *gapGeom = parts.next();
151 // Skip the gap between features and boundingbox
152 const double spacing = context()->tolerance;
153 if ( gapGeom->boundingBox().snappedToGrid( spacing ) == envelope->boundingBox().snappedToGrid( spacing ) )
154 {
155 continue;
156 }
157
158 // Skip gaps above threshold
159 if ( ( mGapThresholdMapUnits > 0 && gapGeom->area() > mGapThresholdMapUnits ) || gapGeom->area() < mContext->reducedTolerance )
160 {
161 continue;
162 }
163
164 QgsRectangle gapAreaBBox = gapGeom->boundingBox();
165
166 // Get neighboring polygons
167 QMap<QString, QgsFeatureIds> neighboringIds;
168 const QgsGeometryCheckerUtils::LayerFeatures layerFeatures( featurePools, featureIds.keys(), gapAreaBBox, compatibleGeometryTypes(), mContext );
169 std::unique_ptr< QgsGeometryEngine > gapGeomEngine = QgsGeometryCheckerUtils::createGeomEngine( gapGeom, mContext->tolerance );
170 gapGeomEngine->prepareGeometry();
171 for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeature : layerFeatures )
172 {
173 const QgsGeometry geom = layerFeature.geometry();
174 if ( gapGeomEngine->distance( geom.constGet() ) < mContext->tolerance )
175 {
176 neighboringIds[layerFeature.layer()->id()].insert( layerFeature.feature().id() );
177 gapAreaBBox.combineExtentWith( geom.boundingBox() );
178 }
179 }
180
181 if ( neighboringIds.isEmpty() )
182 {
183 continue;
184 }
185
186 if ( allowedGapsGeomEngine && allowedGapsGeomEngine->contains( gapGeom ) )
187 {
188 continue;
189 }
190
191 // Add error
192 double area = gapGeom->area();
193 QgsRectangle gapBbox = gapGeom->boundingBox();
194 errors.append( new QgsGeometryGapCheckError( this, QString(), QgsGeometry( gapGeom->clone() ), neighboringIds, area, gapBbox, gapAreaBBox ) );
195 }
196 }
197
fixError(const QMap<QString,QgsFeaturePool * > & featurePools,QgsGeometryCheckError * error,int method,const QMap<QString,int> &,Changes & changes) const198 void QgsGeometryGapCheck::fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> & /*mergeAttributeIndices*/, Changes &changes ) const
199 {
200 QMetaEnum metaEnum = QMetaEnum::fromType<QgsGeometryGapCheck::ResolutionMethod>();
201 if ( !metaEnum.isValid() || !metaEnum.valueToKey( method ) )
202 {
203 error->setFixFailed( tr( "Unknown method" ) );
204 }
205 else
206 {
207 ResolutionMethod methodValue = static_cast<ResolutionMethod>( method );
208 switch ( methodValue )
209 {
210 case NoChange:
211 error->setFixed( method );
212 break;
213
214 case MergeLongestEdge:
215 {
216 QString errMsg;
217 if ( mergeWithNeighbor( featurePools, static_cast<QgsGeometryGapCheckError *>( error ), changes, errMsg, LongestSharedEdge ) )
218 {
219 error->setFixed( method );
220 }
221 else
222 {
223 error->setFixFailed( tr( "Failed to merge with neighbor: %1" ).arg( errMsg ) );
224 }
225 break;
226 }
227
228 case AddToAllowedGaps:
229 {
230 QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( mAllowedGapsLayer.data() );
231 if ( layer )
232 {
233 if ( !layer->isEditable() && !layer->startEditing() )
234 {
235 error->setFixFailed( tr( "Could not start editing layer %1" ).arg( layer->name() ) );
236 }
237 else
238 {
239 QgsFeature feature = QgsVectorLayerUtils::createFeature( layer, error->geometry() );
240 QgsFeatureList features = QgsVectorLayerUtils::makeFeatureCompatible( feature, layer );
241 if ( !layer->addFeatures( features ) )
242 {
243 error->setFixFailed( tr( "Could not add feature to layer %1" ).arg( layer->name() ) );
244 }
245 else
246 {
247 error->setFixed( method );
248 }
249 }
250 }
251 else
252 {
253 error->setFixFailed( tr( "Allowed gaps layer could not be resolved" ) );
254 }
255 break;
256 }
257
258 case CreateNewFeature:
259 {
260 QgsGeometryGapCheckError *gapCheckError = static_cast<QgsGeometryGapCheckError *>( error );
261 QgsProject *project = QgsProject::instance();
262 QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( project->mapLayer( gapCheckError->neighbors().keys().first() ) );
263 if ( layer )
264 {
265 const QgsGeometry geometry = error->geometry();
266 QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( layer ) );
267 QgsFeature feature = QgsVectorLayerUtils::createFeature( layer, geometry, QgsAttributeMap(), &context );
268 if ( !layer->addFeature( feature ) )
269 {
270 error->setFixFailed( tr( "Could not add feature" ) );
271 }
272 else
273 {
274 error->setFixed( method );
275 }
276 }
277 else
278 {
279 error->setFixFailed( tr( "Could not resolve target layer %1 to add feature" ).arg( error->layerId() ) );
280 }
281 break;
282 }
283
284 case MergeLargestArea:
285 {
286 QString errMsg;
287 if ( mergeWithNeighbor( featurePools, static_cast<QgsGeometryGapCheckError *>( error ), changes, errMsg, LargestArea ) )
288 {
289 error->setFixed( method );
290 }
291 else
292 {
293 error->setFixFailed( tr( "Failed to merge with neighbor: %1" ).arg( errMsg ) );
294 }
295 break;
296 }
297 }
298 }
299 }
300
mergeWithNeighbor(const QMap<QString,QgsFeaturePool * > & featurePools,QgsGeometryGapCheckError * err,Changes & changes,QString & errMsg,Condition condition) const301 bool QgsGeometryGapCheck::mergeWithNeighbor( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryGapCheckError *err, Changes &changes, QString &errMsg, Condition condition ) const
302 {
303 double maxVal = 0.;
304 QString mergeLayerId;
305 QgsFeature mergeFeature;
306 int mergePartIdx = -1;
307
308 const QgsGeometry geometry = err->geometry();
309 const QgsAbstractGeometry *errGeometry = QgsGeometryCheckerUtils::getGeomPart( geometry.constGet(), 0 );
310
311 const auto layerIds = err->neighbors().keys();
312 QList<QgsFeature> neighbours;
313
314 // Search for touching neighboring geometries
315 for ( const QString &layerId : layerIds )
316 {
317 QgsFeaturePool *featurePool = featurePools.value( layerId );
318 std::unique_ptr<QgsAbstractGeometry> errLayerGeom( errGeometry->clone() );
319 QgsCoordinateTransform ct( featurePool->crs(), mContext->mapCrs, mContext->transformContext );
320 errLayerGeom->transform( ct, QgsCoordinateTransform::ReverseTransform );
321
322 const auto featureIds = err->neighbors().value( layerId );
323
324 for ( QgsFeatureId testId : featureIds )
325 {
326 QgsFeature feature;
327 if ( !featurePool->getFeature( testId, feature ) )
328 {
329 continue;
330 }
331
332 QgsGeometry transformedGeometry = feature.geometry();
333 transformedGeometry.transform( ct );
334 feature.setGeometry( transformedGeometry );
335 neighbours.append( feature );
336 }
337
338 for ( const QgsFeature &testFeature : neighbours )
339 {
340 const QgsGeometry featureGeom = testFeature.geometry();
341 const QgsAbstractGeometry *testGeom = featureGeom.constGet();
342 for ( int iPart = 0, nParts = testGeom->partCount(); iPart < nParts; ++iPart )
343 {
344 double val = 0;
345 switch ( condition )
346 {
347 case LongestSharedEdge:
348 val = QgsGeometryCheckerUtils::sharedEdgeLength( errLayerGeom.get(), QgsGeometryCheckerUtils::getGeomPart( testGeom, iPart ), mContext->reducedTolerance );
349 break;
350
351 case LargestArea:
352 // We might get a neighbour where we touch only a corner
353 if ( QgsGeometryCheckerUtils::sharedEdgeLength( errLayerGeom.get(), QgsGeometryCheckerUtils::getGeomPart( testGeom, iPart ), mContext->reducedTolerance ) > 0 )
354 val = QgsGeometryCheckerUtils::getGeomPart( testGeom, iPart )->area();
355 break;
356 }
357
358 if ( val > maxVal )
359 {
360 maxVal = val;
361 mergeFeature = testFeature;
362 mergePartIdx = iPart;
363 mergeLayerId = layerId;
364 }
365 }
366 }
367 }
368
369 if ( maxVal == 0. )
370 {
371 return false;
372 }
373
374 // Create an index of all neighbouring vertices
375 QgsSpatialIndex neighbourVerticesIndex( QgsSpatialIndex::Flag::FlagStoreFeatureGeometries );
376 int id = 0;
377 for ( const QgsFeature &neighbour : neighbours )
378 {
379 QgsVertexIterator vit = neighbour.geometry().vertices();
380 while ( vit.hasNext() )
381 {
382 QgsPoint pt = vit.next();
383 QgsFeature f;
384 f.setId( id ); // required for SpatialIndex to return the correct result
385 f.setGeometry( QgsGeometry( pt.clone() ) );
386 neighbourVerticesIndex.addFeature( f );
387 id++;
388 }
389 }
390
391 // Snap to the closest vertex
392 QgsPolyline snappedRing;
393 QgsVertexIterator iterator = errGeometry->vertices();
394 while ( iterator.hasNext() )
395 {
396 QgsPoint pt = iterator.next();
397 QgsGeometry closestGeom = neighbourVerticesIndex.geometry( neighbourVerticesIndex.nearestNeighbor( QgsPointXY( pt ) ).first() );
398 if ( !closestGeom.isEmpty() )
399 {
400 snappedRing.append( QgsPoint( closestGeom.vertexAt( 0 ) ) );
401 }
402 }
403
404 std::unique_ptr<QgsPolygon> snappedErrGeom = qgis::make_unique<QgsPolygon>();
405 snappedErrGeom->setExteriorRing( new QgsLineString( snappedRing ) );
406
407 // Merge geometries
408 QgsFeaturePool *featurePool = featurePools[ mergeLayerId ];
409 std::unique_ptr<QgsAbstractGeometry> errLayerGeom( snappedErrGeom->clone() );
410 QgsCoordinateTransform ct( featurePool->crs(), mContext->mapCrs, mContext->transformContext );
411 errLayerGeom->transform( ct, QgsCoordinateTransform::ReverseTransform );
412 const QgsGeometry mergeFeatureGeom = mergeFeature.geometry();
413 const QgsAbstractGeometry *mergeGeom = mergeFeatureGeom.constGet();
414 std::unique_ptr< QgsGeometryEngine > geomEngine = QgsGeometryCheckerUtils::createGeomEngine( errLayerGeom.get(), 0 );
415 std::unique_ptr<QgsAbstractGeometry> combinedGeom( geomEngine->combine( QgsGeometryCheckerUtils::getGeomPart( mergeGeom, mergePartIdx ), &errMsg ) );
416 if ( !combinedGeom || combinedGeom->isEmpty() || !QgsWkbTypes::isSingleType( combinedGeom->wkbType() ) )
417 {
418 return false;
419 }
420
421 // Add merged polygon to destination geometry
422 replaceFeatureGeometryPart( featurePools, mergeLayerId, mergeFeature, mergePartIdx, combinedGeom.release(), changes );
423
424 return true;
425 }
426
427
resolutionMethods() const428 QStringList QgsGeometryGapCheck::resolutionMethods() const
429 {
430 QStringList methods = QStringList()
431 << tr( "Add gap area to neighboring polygon with longest shared edge" )
432 << tr( "No action" );
433 if ( mAllowedGapsSource )
434 methods << tr( "Add gap to allowed exceptions" );
435
436 return methods;
437 }
438
availableResolutionMethods() const439 QList<QgsGeometryCheckResolutionMethod> QgsGeometryGapCheck::availableResolutionMethods() const
440 {
441 QList<QgsGeometryCheckResolutionMethod> fixes
442 {
443 QgsGeometryCheckResolutionMethod( MergeLongestEdge, tr( "Add to longest shared edge" ), tr( "Add the gap area to the neighbouring polygon with the longest shared edge." ), false ),
444 QgsGeometryCheckResolutionMethod( CreateNewFeature, tr( "Create new feature" ), tr( "Create a new feature from the gap area." ), false ),
445 QgsGeometryCheckResolutionMethod( MergeLargestArea, tr( "Add to largest neighbouring area" ), tr( "Add the gap area to the neighbouring polygon with the largest area." ), false )
446 };
447
448 if ( mAllowedGapsSource )
449 fixes << QgsGeometryCheckResolutionMethod( AddToAllowedGaps, tr( "Add Gap to Allowed Exceptions" ), tr( "Create a new feature from the gap geometry on the allowed exceptions layer." ), true );
450
451 fixes << QgsGeometryCheckResolutionMethod( NoChange, tr( "No action" ), tr( "Do not perform any action and mark this error as fixed." ), false );
452
453 return fixes;
454 }
455
description() const456 QString QgsGeometryGapCheck::description() const
457 {
458 return factoryDescription();
459 }
460
id() const461 QString QgsGeometryGapCheck::id() const
462 {
463 return factoryId();
464 }
465
flags() const466 QgsGeometryCheck::Flags QgsGeometryGapCheck::flags() const
467 {
468 return factoryFlags();
469 }
470
471 ///@cond private
factoryDescription()472 QString QgsGeometryGapCheck::factoryDescription()
473 {
474 return tr( "Gap" );
475 }
476
factoryId()477 QString QgsGeometryGapCheck::factoryId()
478 {
479 return QStringLiteral( "QgsGeometryGapCheck" );
480 }
481
factoryFlags()482 QgsGeometryCheck::Flags QgsGeometryGapCheck::factoryFlags()
483 {
484 return QgsGeometryCheck::AvailableInValidation;
485 }
486
factoryCompatibleGeometryTypes()487 QList<QgsWkbTypes::GeometryType> QgsGeometryGapCheck::factoryCompatibleGeometryTypes()
488 {
489 return {QgsWkbTypes::PolygonGeometry};
490 }
491
factoryIsCompatible(QgsVectorLayer * layer)492 bool QgsGeometryGapCheck::factoryIsCompatible( QgsVectorLayer *layer ) SIP_SKIP
493 {
494 return factoryCompatibleGeometryTypes().contains( layer->geometryType() );
495 }
496
factoryCheckType()497 QgsGeometryCheck::CheckType QgsGeometryGapCheck::factoryCheckType()
498 {
499 return QgsGeometryCheck::LayerCheck;
500 }
501 ///@endcond private
502
contextBoundingBox() const503 QgsRectangle QgsGeometryGapCheckError::contextBoundingBox() const
504 {
505 return mContextBoundingBox;
506 }
507
isEqual(QgsGeometryCheckError * other) const508 bool QgsGeometryGapCheckError::isEqual( QgsGeometryCheckError *other ) const
509 {
510 QgsGeometryGapCheckError *err = dynamic_cast<QgsGeometryGapCheckError *>( other );
511 return err && QgsGeometryCheckerUtils::pointsFuzzyEqual( err->location(), location(), mCheck->context()->reducedTolerance ) && err->neighbors() == neighbors();
512 }
513
closeMatch(QgsGeometryCheckError * other) const514 bool QgsGeometryGapCheckError::closeMatch( QgsGeometryCheckError *other ) const
515 {
516 QgsGeometryGapCheckError *err = dynamic_cast<QgsGeometryGapCheckError *>( other );
517 return err && err->layerId() == layerId() && err->neighbors() == neighbors();
518 }
519
update(const QgsGeometryCheckError * other)520 void QgsGeometryGapCheckError::update( const QgsGeometryCheckError *other )
521 {
522 QgsGeometryCheckError::update( other );
523 // Static cast since this should only get called if isEqual == true
524 const QgsGeometryGapCheckError *err = static_cast<const QgsGeometryGapCheckError *>( other );
525 mNeighbors = err->mNeighbors;
526 mGapAreaBBox = err->mGapAreaBBox;
527 }
528
handleChanges(const QgsGeometryCheck::Changes &)529 bool QgsGeometryGapCheckError::handleChanges( const QgsGeometryCheck::Changes & )
530 {
531 return true;
532 }
533
affectedAreaBBox() const534 QgsRectangle QgsGeometryGapCheckError::affectedAreaBBox() const
535 {
536 return mGapAreaBBox;
537 }
538
involvedFeatures() const539 QMap<QString, QgsFeatureIds> QgsGeometryGapCheckError::involvedFeatures() const
540 {
541 return mNeighbors;
542 }
543
icon() const544 QIcon QgsGeometryGapCheckError::icon() const
545 {
546
547 if ( status() == QgsGeometryCheckError::StatusFixed )
548 return QgsApplication::getThemeIcon( QStringLiteral( "/algorithms/mAlgorithmCheckGeometry.svg" ) );
549 else
550 return QgsApplication::getThemeIcon( QStringLiteral( "/checks/SliverOrGap.svg" ) );
551 }
552