QGIS API Documentation  3.8.0-Zanzibar (11aff65)
qgsgeometrygapcheck.cpp
Go to the documentation of this file.
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 
17 #include "qgsgeometryengine.h"
18 #include "qgsgeometrygapcheck.h"
19 #include "qgsgeometrycollection.h"
20 #include "qgsfeaturepool.h"
21 #include "qgsvectorlayer.h"
22 #include "qgsfeedback.h"
23 #include "qgsapplication.h"
24 
25 #include "geos_c.h"
26 
27 QgsGeometryGapCheck::QgsGeometryGapCheck( const QgsGeometryCheckContext *context, const QVariantMap &configuration )
28  : QgsGeometryCheck( context, configuration )
29  , mGapThresholdMapUnits( configuration.value( QStringLiteral( "gapThreshold" ) ).toDouble() )
30 
31 {
32 
33 }
34 
35 void QgsGeometryGapCheck::collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids ) const
36 {
37  if ( feedback )
38  feedback->setProgress( feedback->progress() + 1.0 );
39 
40  QVector<QgsGeometry> geomList;
41 
42 
43  QMap<QString, QgsFeatureIds> featureIds = ids.isEmpty() ? allLayerFeatureIds( featurePools ) : ids.toMap();
44  const QgsGeometryCheckerUtils::LayerFeatures layerFeatures( featurePools, featureIds, compatibleGeometryTypes(), nullptr, mContext, true );
45  for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeature : layerFeatures )
46  {
47  geomList.append( layerFeature.geometry() );
48 
49  if ( feedback && feedback->isCanceled() )
50  {
51  geomList.clear();
52  break;
53  }
54  }
55 
56  if ( geomList.isEmpty() )
57  {
58  return;
59  }
60 
61  std::unique_ptr< QgsGeometryEngine > geomEngine = QgsGeometryCheckerUtils::createGeomEngine( nullptr, mContext->tolerance );
62  geomEngine->prepareGeometry();
63 
64  // Create union of geometry
65  QString errMsg;
66  std::unique_ptr<QgsAbstractGeometry> unionGeom( geomEngine->combine( geomList, &errMsg ) );
67  if ( !unionGeom )
68  {
69  messages.append( tr( "Gap check: %1" ).arg( errMsg ) );
70  return;
71  }
72 
73  // Get envelope of union
74  geomEngine = QgsGeometryCheckerUtils::createGeomEngine( unionGeom.get(), mContext->tolerance );
75  geomEngine->prepareGeometry();
76  std::unique_ptr<QgsAbstractGeometry> envelope( geomEngine->envelope( &errMsg ) );
77  if ( !envelope )
78  {
79  messages.append( tr( "Gap check: %1" ).arg( errMsg ) );
80  return;
81  }
82 
83  // Buffer envelope
84  geomEngine = QgsGeometryCheckerUtils::createGeomEngine( envelope.get(), mContext->tolerance );
85  geomEngine->prepareGeometry();
86  QgsAbstractGeometry *bufEnvelope = geomEngine->buffer( 2, 0, GEOSBUF_CAP_SQUARE, GEOSBUF_JOIN_MITRE, 4. ); //#spellok //#spellok
87  envelope.reset( bufEnvelope );
88 
89  // Compute difference between envelope and union to obtain gap polygons
90  geomEngine = QgsGeometryCheckerUtils::createGeomEngine( envelope.get(), mContext->tolerance );
91  geomEngine->prepareGeometry();
92  std::unique_ptr<QgsAbstractGeometry> diffGeom( geomEngine->difference( unionGeom.get(), &errMsg ) );
93  if ( !diffGeom )
94  {
95  messages.append( tr( "Gap check: %1" ).arg( errMsg ) );
96  return;
97  }
98 
99  // For each gap polygon which does not lie on the boundary, get neighboring polygons and add error
100  for ( int iPart = 0, nParts = diffGeom->partCount(); iPart < nParts; ++iPart )
101  {
102  std::unique_ptr<QgsAbstractGeometry> gapGeom( QgsGeometryCheckerUtils::getGeomPart( diffGeom.get(), iPart )->clone() );
103  // Skip the gap between features and boundingbox
104  const double spacing = context()->tolerance;
105  if ( gapGeom->boundingBox().snappedToGrid( spacing ) == envelope->boundingBox().snappedToGrid( spacing ) )
106  {
107  continue;
108  }
109 
110  // Skip gaps above threshold
111  if ( ( mGapThresholdMapUnits > 0 && gapGeom->area() > mGapThresholdMapUnits ) || gapGeom->area() < mContext->reducedTolerance )
112  {
113  continue;
114  }
115 
116  QgsRectangle gapAreaBBox = gapGeom->boundingBox();
117 
118  // Get neighboring polygons
119  QMap<QString, QgsFeatureIds> neighboringIds;
120  const QgsGeometryCheckerUtils::LayerFeatures layerFeatures( featurePools, featureIds.keys(), gapAreaBBox, compatibleGeometryTypes(), mContext );
121  for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeature : layerFeatures )
122  {
123  const QgsGeometry geom = layerFeature.geometry();
125  {
126  neighboringIds[layerFeature.layer()->id()].insert( layerFeature.feature().id() );
127  gapAreaBBox.combineExtentWith( layerFeature.geometry().boundingBox() );
128  }
129  }
130 
131  if ( neighboringIds.isEmpty() )
132  {
133  continue;
134  }
135 
136  // Add error
137  double area = gapGeom->area();
138  errors.append( new QgsGeometryGapCheckError( this, QString(), QgsGeometry( gapGeom.release() ), neighboringIds, area, gapAreaBBox ) );
139  }
140 }
141 
142 void QgsGeometryGapCheck::fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> & /*mergeAttributeIndices*/, Changes &changes ) const
143 {
144  QMetaEnum metaEnum = QMetaEnum::fromType<QgsGeometryGapCheck::ResolutionMethod>();
145  if ( !metaEnum.isValid() || !metaEnum.valueToKey( method ) )
146  {
147  error->setFixFailed( tr( "Unknown method" ) );
148  }
149  else
150  {
151  ResolutionMethod methodValue = static_cast<ResolutionMethod>( method );
152  switch ( methodValue )
153  {
154  case NoChange:
155  error->setFixed( method );
156  break;
157  case MergeLongestEdge:
158  QString errMsg;
159  if ( mergeWithNeighbor( featurePools, static_cast<QgsGeometryGapCheckError *>( error ), changes, errMsg ) )
160  {
161  error->setFixed( method );
162  }
163  else
164  {
165  error->setFixFailed( tr( "Failed to merge with neighbor: %1" ).arg( errMsg ) );
166  }
167  break;
168  }
169  }
170 }
171 
172 bool QgsGeometryGapCheck::mergeWithNeighbor( const QMap<QString, QgsFeaturePool *> &featurePools,
174  Changes &changes, QString &errMsg ) const
175 {
176  double maxVal = 0.;
177  QString mergeLayerId;
178  QgsFeature mergeFeature;
179  int mergePartIdx = -1;
180 
181  const QgsGeometry geometry = err->geometry();
182  const QgsAbstractGeometry *errGeometry = QgsGeometryCheckerUtils::getGeomPart( geometry.constGet(), 0 );
183 
184  const auto layerIds = err->neighbors().keys();
185  // Search for touching neighboring geometries
186  for ( const QString &layerId : layerIds )
187  {
188  QgsFeaturePool *featurePool = featurePools.value( layerId );
189  std::unique_ptr<QgsAbstractGeometry> errLayerGeom( errGeometry->clone() );
192 
193  const auto featureIds = err->neighbors().value( layerId );
194 
195  for ( QgsFeatureId testId : featureIds )
196  {
197  QgsFeature testFeature;
198  if ( !featurePool->getFeature( testId, testFeature ) )
199  {
200  continue;
201  }
202  const QgsGeometry featureGeom = testFeature.geometry();
203  const QgsAbstractGeometry *testGeom = featureGeom.constGet();
204  for ( int iPart = 0, nParts = testGeom->partCount(); iPart < nParts; ++iPart )
205  {
206  double len = QgsGeometryCheckerUtils::sharedEdgeLength( errLayerGeom.get(), QgsGeometryCheckerUtils::getGeomPart( testGeom, iPart ), mContext->reducedTolerance );
207  if ( len > maxVal )
208  {
209  maxVal = len;
210  mergeFeature = testFeature;
211  mergePartIdx = iPart;
212  mergeLayerId = layerId;
213  }
214  }
215  }
216  }
217 
218  if ( maxVal == 0. )
219  {
220  return false;
221  }
222 
223  // Merge geometries
224  QgsFeaturePool *featurePool = featurePools[ mergeLayerId ];
225  std::unique_ptr<QgsAbstractGeometry> errLayerGeom( errGeometry->clone() );
228  const QgsGeometry mergeFeatureGeom = mergeFeature.geometry();
229  const QgsAbstractGeometry *mergeGeom = mergeFeatureGeom.constGet();
230  std::unique_ptr< QgsGeometryEngine > geomEngine = QgsGeometryCheckerUtils::createGeomEngine( errLayerGeom.get(), mContext->reducedTolerance );
231  std::unique_ptr<QgsAbstractGeometry> combinedGeom( geomEngine->combine( QgsGeometryCheckerUtils::getGeomPart( mergeGeom, mergePartIdx ), &errMsg ) );
232  if ( !combinedGeom || combinedGeom->isEmpty() || !QgsWkbTypes::isSingleType( combinedGeom->wkbType() ) )
233  {
234  return false;
235  }
236 
237  // Add merged polygon to destination geometry
238  replaceFeatureGeometryPart( featurePools, mergeLayerId, mergeFeature, mergePartIdx, combinedGeom.release(), changes );
239 
240  return true;
241 }
242 
243 
245 {
246  static QStringList methods = QStringList() << tr( "Add gap area to neighboring polygon with longest shared edge" ) << tr( "No action" );
247  return methods;
248 }
249 
251 {
252  return factoryDescription();
253 }
254 
255 QString QgsGeometryGapCheck::id() const
256 {
257  return factoryId();
258 }
259 
260 QgsGeometryCheck::Flags QgsGeometryGapCheck::flags() const
261 {
262  return factoryFlags();
263 }
264 
266 QString QgsGeometryGapCheck::factoryDescription()
267 {
268  return tr( "Gap" );
269 }
270 
271 QString QgsGeometryGapCheck::factoryId()
272 {
273  return QStringLiteral( "QgsGeometryGapCheck" );
274 }
275 
276 QgsGeometryCheck::Flags QgsGeometryGapCheck::factoryFlags()
277 {
279 }
280 
281 QList<QgsWkbTypes::GeometryType> QgsGeometryGapCheck::factoryCompatibleGeometryTypes()
282 {
284 }
285 
286 bool QgsGeometryGapCheck::factoryIsCompatible( QgsVectorLayer *layer ) SIP_SKIP
287 {
288  return factoryCompatibleGeometryTypes().contains( layer->geometryType() );
289 }
290 
291 QgsGeometryCheck::CheckType QgsGeometryGapCheck::factoryCheckType()
292 {
294 }
296 
298 {
299  QgsGeometryGapCheckError *err = dynamic_cast<QgsGeometryGapCheckError *>( other );
300  return err && QgsGeometryCheckerUtils::pointsFuzzyEqual( err->location(), location(), mCheck->context()->reducedTolerance ) && err->neighbors() == neighbors();
301 }
302 
304 {
305  QgsGeometryGapCheckError *err = dynamic_cast<QgsGeometryGapCheckError *>( other );
306  return err && err->layerId() == layerId() && err->neighbors() == neighbors();
307 }
308 
310 {
312  // Static cast since this should only get called if isEqual == true
313  const QgsGeometryGapCheckError *err = static_cast<const QgsGeometryGapCheckError *>( other );
314  mNeighbors = err->mNeighbors;
315  mGapAreaBBox = err->mGapAreaBBox;
316 }
317 
319 {
320  return true;
321 }
322 
324 {
325  return mGapAreaBBox;
326 }
327 
328 QMap<QString, QgsFeatureIds> QgsGeometryGapCheckError::involvedFeatures() const
329 {
330  return mNeighbors;
331 }
332 
334 {
335 
336  if ( status() == QgsGeometryCheckError::StatusFixed )
337  return QgsApplication::getThemeIcon( QStringLiteral( "/algorithms/mAlgorithmCheckGeometry.svg" ) );
338  else
339  return QgsApplication::getThemeIcon( QStringLiteral( "/checks/SliverOrGap.svg" ) );
340 }
A rectangle specified with double values.
Definition: qgsrectangle.h:41
static bool pointsFuzzyEqual(const QgsPointXY &p1, const QgsPointXY &p2, double tol)
Determine whether two points are equal up to the specified tolerance.
QList< QgsWkbTypes::GeometryType > compatibleGeometryTypes() const override
A list of geometry types for which this check can be performed.
virtual void update(const QgsGeometryCheckError *other)
Update this error with the information from other.
bool closeMatch(QgsGeometryCheckError *other) const override
Check if this error is almost equal to other.
static QgsAbstractGeometry * getGeomPart(QgsAbstractGeometry *geom, int partIdx)
double progress() const
Returns the current progress reported by the feedback object.
Definition: qgsfeedback.h:80
const QgsCoordinateReferenceSystem mapCrs
The coordinate system in which calculations should be done.
QgsPointXY transform(const QgsPointXY &point, TransformDirection direction=ForwardTransform) const SIP_THROW(QgsCsException)
Transform the point from the source CRS to the destination CRS.
QString description() const override
Returns a human readable description for this check.
const QgsPointXY & location() const
The location of the error in map units.
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition: qgsfeedback.h:63
QMap< QString, QgsFeatureIds > toMap() const
qint64 QgsFeatureId
Definition: qgsfeatureid.h:25
Contains a set of layers and feature ids in those layers to pass to a geometry check.
CheckType
The type of a check.
QgsWkbTypes::GeometryType geometryType() const
Returns point, line or polygon.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:111
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:55
virtual QgsAbstractGeometry * clone() const =0
Clones the geometry by performing a deep copy.
void replaceFeatureGeometryPart(const QMap< QString, QgsFeaturePool *> &featurePools, const QString &layerId, QgsFeature &feature, int partIdx, QgsAbstractGeometry *newPartGeom, Changes &changes) const
Replaces a part in a feature geometry.
void setFixFailed(const QString &reason)
Set the error status to failed and specify the reason for failure.
QIcon icon() const override
Returns an icon that should be shown for this kind of error.
Base class for feedback objects to be used for cancellation of something running in a worker thread...
Definition: qgsfeedback.h:44
Base configuration for geometry checks.
An error produced by a QgsGeometryGapCheck.
static double sharedEdgeLength(const QgsAbstractGeometry *geom1, const QgsAbstractGeometry *geom2, double tol)
void update(const QgsGeometryCheckError *other) override
Update this error with the information from other.
#define SIP_SKIP
Definition: qgis_sip.h:119
QStringList resolutionMethods() const override
Returns a list of descriptions for available resolutions for errors.
bool getFeature(QgsFeatureId id, QgsFeature &feature)
Retrieves the feature with the specified id into feature.
bool isEqual(QgsGeometryCheckError *other) const override
Check if this error is equal to other.
A layer feature combination to uniquely identify and access a feature in a set of layers...
This class implements a geometry check.
QgsGeometryCheck::Flags flags() const override
Flags for this geometry check.
Abstract base class for all geometries.
QMap< QString, QgsFeatureIds > allLayerFeatureIds(const QMap< QString, QgsFeaturePool *> &featurePools) const
Returns all layers and feature ids.
const QString & layerId() const
The id of the layer on which this error has been detected.
const double tolerance
The tolerance to allow for in geometry checks.
ResolutionMethod
Resolution methods for geometry gap checks.
const QgsGeometryCheckContext * mContext
const double reducedTolerance
The tolerance to allow for in geometry checks.
A list of layers and feature ids for each of these layers.
const QMap< QString, QgsFeatureIds > & neighbors() const
A map of layers and feature ids of the neighbors of the gap.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
QgsGeometry geometry() const
The geometry of the error in map units.
static bool isSingleType(Type type)
Returns true if the WKB type is a single type.
Definition: qgswkbtypes.h:550
void combineExtentWith(const QgsRectangle &rect)
Expands the rectangle so that it covers both the original rectangle and the given rectangle...
Definition: qgsrectangle.h:359
QgsGeometryGapCheck(const QgsGeometryCheckContext *context, const QVariantMap &configuration)
The configuration accepts a "gapThreshold" key which specifies the maximum gap size in squared map un...
QMap< QString, QgsFeatureIds > involvedFeatures() const override
Returns a list of involved features.
QMap< QString, QMap< QgsFeatureId, QList< QgsGeometryCheck::Change > > > Changes
A collection of changes.
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
Transform from destination to source CRS.
void fixError(const QMap< QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap< QString, int > &mergeAttributeIndices, Changes &changes) const override
Fix the error error with the specified method.
bool handleChanges(const QgsGeometryCheck::Changes &) override
Apply a list of changes.
A feature pool is based on a vector layer and caches features.
QgsRectangle affectedAreaBBox() const override
The bounding box of the affected area of the error.
void setFixed(int method)
Set the status to fixed and specify the method that has been used to fix the error.
Do not handle the error.
Class for doing transforms between two map coordinate systems.
void collectErrors(const QMap< QString, QgsFeaturePool *> &featurePools, QList< QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids=LayerFeatureIds()) const override
The main worker method.
const QgsCoordinateTransformContext transformContext
The coordinate transform context with which transformations will be done.
QgsCoordinateReferenceSystem crs() const
The coordinate reference system of this layer.
QgsGeometry geometry
Definition: qgsfeature.h:67
const QgsGeometryCheckContext * context() const
Returns the context.
This represents an error reported by a geometry check.
static std::unique_ptr< QgsGeometryEngine > createGeomEngine(const QgsAbstractGeometry *geometry, double tolerance)
Represents a vector layer which manages a vector based data sets.
QString id() const override
Returns an id for this check.
Merge the gap with the polygon with the longest shared edge.
This geometry check should be available in layer validation on the vector layer peroperties.
virtual int partCount() const =0
Returns count of parts contained in the geometry.
The check controls a whole layer (topology checks)