QGIS API Documentation  3.6.0-Noosa (5873452)
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 
24 #include "geos_c.h"
25 
26 QgsGeometryGapCheck::QgsGeometryGapCheck( const QgsGeometryCheckContext *context, const QVariantMap &configuration )
27  : QgsGeometryCheck( context, configuration )
28  , mGapThresholdMapUnits( configuration.value( QStringLiteral( "gapThreshold" ) ).toDouble() )
29 
30 {
31 
32 }
33 
34 void QgsGeometryGapCheck::collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids ) const
35 {
36  if ( feedback )
37  feedback->setProgress( feedback->progress() + 1.0 );
38 
39  QVector<QgsGeometry> geomList;
40 
41 
42  QMap<QString, QgsFeatureIds> featureIds = ids.isEmpty() ? allLayerFeatureIds( featurePools ) : ids.toMap();
43  const QgsGeometryCheckerUtils::LayerFeatures layerFeatures( featurePools, featureIds, compatibleGeometryTypes(), nullptr, mContext, true );
44  for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeature : layerFeatures )
45  {
46  geomList.append( layerFeature.geometry() );
47 
48  if ( feedback && feedback->isCanceled() )
49  {
50  geomList.clear();
51  break;
52  }
53  }
54 
55  if ( geomList.isEmpty() )
56  {
57  return;
58  }
59 
60  std::unique_ptr< QgsGeometryEngine > geomEngine = QgsGeometryCheckerUtils::createGeomEngine( nullptr, mContext->tolerance );
61 
62  // Create union of geometry
63  QString errMsg;
64  std::unique_ptr<QgsAbstractGeometry> unionGeom( geomEngine->combine( geomList, &errMsg ) );
65  if ( !unionGeom )
66  {
67  messages.append( tr( "Gap check: %1" ).arg( errMsg ) );
68  return;
69  }
70 
71  // Get envelope of union
72  geomEngine = QgsGeometryCheckerUtils::createGeomEngine( unionGeom.get(), mContext->tolerance );
73  std::unique_ptr<QgsAbstractGeometry> envelope( geomEngine->envelope( &errMsg ) );
74  if ( !envelope )
75  {
76  messages.append( tr( "Gap check: %1" ).arg( errMsg ) );
77  return;
78  }
79 
80  // Buffer envelope
81  geomEngine = QgsGeometryCheckerUtils::createGeomEngine( envelope.get(), mContext->tolerance );
82  QgsAbstractGeometry *bufEnvelope = geomEngine->buffer( 2, 0, GEOSBUF_CAP_SQUARE, GEOSBUF_JOIN_MITRE, 4. ); //#spellok //#spellok
83  envelope.reset( bufEnvelope );
84 
85  // Compute difference between envelope and union to obtain gap polygons
86  geomEngine = QgsGeometryCheckerUtils::createGeomEngine( envelope.get(), mContext->tolerance );
87  std::unique_ptr<QgsAbstractGeometry> diffGeom( geomEngine->difference( unionGeom.get(), &errMsg ) );
88  if ( !diffGeom )
89  {
90  messages.append( tr( "Gap check: %1" ).arg( errMsg ) );
91  return;
92  }
93 
94  // For each gap polygon which does not lie on the boundary, get neighboring polygons and add error
95  for ( int iPart = 0, nParts = diffGeom->partCount(); iPart < nParts; ++iPart )
96  {
97  std::unique_ptr<QgsAbstractGeometry> gapGeom( QgsGeometryCheckerUtils::getGeomPart( diffGeom.get(), iPart )->clone() );
98  // Skip the gap between features and boundingbox
99  const double spacing = context()->tolerance;
100  if ( gapGeom->boundingBox().snappedToGrid( spacing ) == envelope->boundingBox().snappedToGrid( spacing ) )
101  {
102  continue;
103  }
104 
105  // Skip gaps above threshold
106  if ( ( mGapThresholdMapUnits > 0 && gapGeom->area() > mGapThresholdMapUnits ) || gapGeom->area() < mContext->reducedTolerance )
107  {
108  continue;
109  }
110 
111  QgsRectangle gapAreaBBox = gapGeom->boundingBox();
112 
113  // Get neighboring polygons
114  QMap<QString, QgsFeatureIds> neighboringIds;
115  const QgsGeometryCheckerUtils::LayerFeatures layerFeatures( featurePools, featureIds.keys(), gapAreaBBox, compatibleGeometryTypes(), mContext );
116  for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeature : layerFeatures )
117  {
118  if ( QgsGeometryCheckerUtils::sharedEdgeLength( gapGeom.get(), layerFeature.geometry().constGet(), mContext->reducedTolerance ) > 0 )
119  {
120  neighboringIds[layerFeature.layer()->id()].insert( layerFeature.feature().id() );
121  gapAreaBBox.combineExtentWith( layerFeature.geometry().constGet()->boundingBox() );
122  }
123  }
124 
125  if ( neighboringIds.isEmpty() )
126  {
127  continue;
128  }
129 
130  // Add error
131  double area = gapGeom->area();
132  errors.append( new QgsGeometryGapCheckError( this, QString(), QgsGeometry( gapGeom.release() ), neighboringIds, area, gapAreaBBox ) );
133  }
134 }
135 
136 void QgsGeometryGapCheck::fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> & /*mergeAttributeIndices*/, Changes &changes ) const
137 {
138  QMetaEnum metaEnum = QMetaEnum::fromType<QgsGeometryGapCheck::ResolutionMethod>();
139  if ( !metaEnum.isValid() || !metaEnum.valueToKey( method ) )
140  {
141  error->setFixFailed( tr( "Unknown method" ) );
142  }
143  else
144  {
145  ResolutionMethod methodValue = static_cast<ResolutionMethod>( method );
146  switch ( methodValue )
147  {
148  case NoChange:
149  error->setFixed( method );
150  break;
151  case MergeLongestEdge:
152  QString errMsg;
153  if ( mergeWithNeighbor( featurePools, static_cast<QgsGeometryGapCheckError *>( error ), changes, errMsg ) )
154  {
155  error->setFixed( method );
156  }
157  else
158  {
159  error->setFixFailed( tr( "Failed to merge with neighbor: %1" ).arg( errMsg ) );
160  }
161  break;
162  }
163  }
164 }
165 
166 bool QgsGeometryGapCheck::mergeWithNeighbor( const QMap<QString, QgsFeaturePool *> &featurePools,
168  Changes &changes, QString &errMsg ) const
169 {
170  double maxVal = 0.;
171  QString mergeLayerId;
172  QgsFeature mergeFeature;
173  int mergePartIdx = -1;
174 
175  const QgsGeometry geometry = err->geometry();
176  const QgsAbstractGeometry *errGeometry = QgsGeometryCheckerUtils::getGeomPart( geometry.constGet(), 0 );
177 
178  const auto layerIds = err->neighbors().keys();
179  // Search for touching neighboring geometries
180  for ( const QString &layerId : layerIds )
181  {
182  QgsFeaturePool *featurePool = featurePools.value( layerId );
183  std::unique_ptr<QgsAbstractGeometry> errLayerGeom( errGeometry->clone() );
186 
187  const auto featureIds = err->neighbors().value( layerId );
188 
189  for ( QgsFeatureId testId : featureIds )
190  {
191  QgsFeature testFeature;
192  if ( !featurePool->getFeature( testId, testFeature ) )
193  {
194  continue;
195  }
196  QgsGeometry featureGeom = testFeature.geometry();
197  const QgsAbstractGeometry *testGeom = featureGeom.constGet();
198  for ( int iPart = 0, nParts = testGeom->partCount(); iPart < nParts; ++iPart )
199  {
200  double len = QgsGeometryCheckerUtils::sharedEdgeLength( errLayerGeom.get(), QgsGeometryCheckerUtils::getGeomPart( testGeom, iPart ), mContext->reducedTolerance );
201  if ( len > maxVal )
202  {
203  maxVal = len;
204  mergeFeature = testFeature;
205  mergePartIdx = iPart;
206  mergeLayerId = layerId;
207  }
208  }
209  }
210  }
211 
212  if ( maxVal == 0. )
213  {
214  return false;
215  }
216 
217  // Merge geometries
218  QgsFeaturePool *featurePool = featurePools[ mergeLayerId ];
219  std::unique_ptr<QgsAbstractGeometry> errLayerGeom( errGeometry->clone() );
222  QgsGeometry mergeFeatureGeom = mergeFeature.geometry();
223  const QgsAbstractGeometry *mergeGeom = mergeFeatureGeom.constGet();
224  std::unique_ptr< QgsGeometryEngine > geomEngine = QgsGeometryCheckerUtils::createGeomEngine( errLayerGeom.get(), mContext->reducedTolerance );
225  std::unique_ptr<QgsAbstractGeometry> combinedGeom( geomEngine->combine( QgsGeometryCheckerUtils::getGeomPart( mergeGeom, mergePartIdx ), &errMsg ) );
226  if ( !combinedGeom || combinedGeom->isEmpty() || !QgsWkbTypes::isSingleType( combinedGeom->wkbType() ) )
227  {
228  return false;
229  }
230 
231  // Add merged polygon to destination geometry
232  replaceFeatureGeometryPart( featurePools, mergeLayerId, mergeFeature, mergePartIdx, combinedGeom.release(), changes );
233 
234  return true;
235 }
236 
237 
239 {
240  static QStringList methods = QStringList() << tr( "Add gap area to neighboring polygon with longest shared edge" ) << tr( "No action" );
241  return methods;
242 }
243 
245 {
246  return factoryDescription();
247 }
248 
249 QString QgsGeometryGapCheck::id() const
250 {
251  return factoryId();
252 }
253 
254 QgsGeometryCheck::Flags QgsGeometryGapCheck::flags() const
255 {
256  return factoryFlags();
257 }
258 
260 QString QgsGeometryGapCheck::factoryDescription()
261 {
262  return tr( "Gap" );
263 }
264 
265 QString QgsGeometryGapCheck::factoryId()
266 {
267  return QStringLiteral( "QgsGeometryGapCheck" );
268 }
269 
270 QgsGeometryCheck::Flags QgsGeometryGapCheck::factoryFlags()
271 {
273 }
274 
275 QList<QgsWkbTypes::GeometryType> QgsGeometryGapCheck::factoryCompatibleGeometryTypes()
276 {
278 }
279 
280 bool QgsGeometryGapCheck::factoryIsCompatible( QgsVectorLayer *layer ) SIP_SKIP
281 {
282  return factoryCompatibleGeometryTypes().contains( layer->geometryType() );
283 }
284 
285 QgsGeometryCheck::CheckType QgsGeometryGapCheck::factoryCheckType()
286 {
288 }
A rectangle specified with double values.
Definition: qgsrectangle.h:41
QList< QgsWkbTypes::GeometryType > compatibleGeometryTypes() const override
A list of geometry types for which this check can be performed.
bool getFeature(QgsFeatureId id, QgsFeature &feature, QgsFeedback *feedback=nullptr)
Retrieves the feature with the specified id into feature.
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.
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:106
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.
Base class for feedback objects to be used for cancelation 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)
#define SIP_SKIP
Definition: qgis_sip.h:119
QStringList resolutionMethods() const override
Returns a list of descriptions for available resolutions for errors.
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 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:549
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, 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.
A feature pool is based on a vector layer and caches features.
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)