QGIS API Documentation  3.23.0-Master (4fd2f04bd0)
qgsgeometryareacheck.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsgeometryareacheck.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 "qgsgeometrycollection.h"
19 #include "qgsgeometryareacheck.h"
20 #include "qgsfeaturepool.h"
21 #include "qgsgeometrycheckerror.h"
22 
23 void QgsGeometryAreaCheck::collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids ) const
24 {
25  Q_UNUSED( messages )
26  const QMap<QString, QgsFeatureIds> featureIds = ids.isEmpty() ? allLayerFeatureIds( featurePools ) : ids.toMap();
27  const QgsGeometryCheckerUtils::LayerFeatures layerFeatures( featurePools, featureIds, compatibleGeometryTypes(), feedback, mContext );
28  for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeature : layerFeatures )
29  {
30  const QgsAbstractGeometry *geom = layerFeature.geometry().constGet();
31  const double layerToMapUnits = scaleFactor( layerFeature.layer() );
32  for ( int iPart = 0, nParts = geom->partCount(); iPart < nParts; ++iPart )
33  {
34  double value;
35  const QgsAbstractGeometry *part = QgsGeometryCheckerUtils::getGeomPart( geom, iPart );
36  if ( checkThreshold( layerToMapUnits, part, value ) )
37  {
38  errors.append( new QgsGeometryCheckError( this, layerFeature, part->centroid(), QgsVertexId( iPart ), value * layerToMapUnits * layerToMapUnits, QgsGeometryCheckError::ValueArea ) );
39  }
40  }
41  }
42 }
43 
44 void QgsGeometryAreaCheck::fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const
45 {
46  QgsFeaturePool *featurePool = featurePools[ error->layerId() ];
47  QgsFeature feature;
48  if ( !featurePool->getFeature( error->featureId(), feature ) )
49  {
50  error->setObsolete();
51  return;
52  }
53 
54  const QgsGeometry g = feature.geometry();
55  const QgsAbstractGeometry *geom = g.constGet();
56  const QgsVertexId vidx = error->vidx();
57 
58  const double layerToMapUnits = scaleFactor( featurePool->layer() );
59 
60  // Check if polygon still exists
61  if ( !vidx.isValid( geom ) )
62  {
63  error->setObsolete();
64  return;
65  }
66 
67  // Check if error still applies
68  double value;
69  if ( !checkThreshold( layerToMapUnits, QgsGeometryCheckerUtils::getGeomPart( geom, vidx.part ), value ) )
70  {
71  error->setObsolete();
72  return;
73  }
74 
75  // Fix with selected method
76  if ( method == NoChange )
77  {
78  error->setFixed( method );
79  }
80  else if ( method == Delete )
81  {
82  deleteFeatureGeometryPart( featurePools, error->layerId(), feature, vidx.part, changes );
83  error->setFixed( method );
84  }
85  else if ( method == MergeLongestEdge || method == MergeLargestArea || method == MergeIdenticalAttribute )
86  {
87  QString errMsg;
88  if ( mergeWithNeighbor( featurePools, error->layerId(), feature, vidx.part, method, mergeAttributeIndices[error->layerId()], changes, errMsg ) )
89  {
90  error->setFixed( method );
91  }
92  else
93  {
94  error->setFixFailed( tr( "Failed to merge with neighbor: %1" ).arg( errMsg ) );
95  }
96  }
97  else
98  {
99  error->setFixFailed( tr( "Unknown method" ) );
100  }
101 }
102 
103 bool QgsGeometryAreaCheck::checkThreshold( double layerToMapUnits, const QgsAbstractGeometry *geom, double &value ) const
104 {
105  value = geom->area();
106  const double threshold = mAreaThreshold / ( layerToMapUnits * layerToMapUnits );
107  return value < threshold;
108 }
109 
110 bool QgsGeometryAreaCheck::mergeWithNeighbor( const QMap<QString, QgsFeaturePool *> &featurePools,
111  const QString &layerId, QgsFeature &feature,
112  int partIdx, int method, int mergeAttributeIndex, Changes &changes, QString &errMsg ) const
113 {
114  QgsFeaturePool *featurePool = featurePools[ layerId ];
115 
116  double maxVal = 0.;
117  QgsFeature mergeFeature;
118  int mergePartIdx = -1;
119  bool matchFound = false;
120  const QgsGeometry featureGeometry = feature.geometry();
121  const QgsAbstractGeometry *geom = featureGeometry.constGet();
122 
123  // Search for touching neighboring geometries
124  const QgsFeatureIds intersects = featurePool->getIntersects( featureGeometry.boundingBox() );
125  for ( const QgsFeatureId testId : intersects )
126  {
127  QgsFeature testFeature;
128  if ( !featurePool->getFeature( testId, testFeature ) )
129  {
130  continue;
131  }
132  const QgsGeometry testFeatureGeom = testFeature.geometry();
133  const QgsAbstractGeometry *testGeom = testFeatureGeom.constGet();
134  for ( int testPartIdx = 0, nTestParts = testGeom->partCount(); testPartIdx < nTestParts; ++testPartIdx )
135  {
136  if ( testId == feature.id() && testPartIdx == partIdx )
137  {
138  continue;
139  }
141  if ( len > 0. )
142  {
143  if ( method == MergeLongestEdge || method == MergeLargestArea )
144  {
145  double val;
146  if ( method == MergeLongestEdge )
147  {
148  val = len;
149  }
150  else
151  {
152  if ( dynamic_cast<const QgsGeometryCollection *>( testGeom ) )
153  val = static_cast<const QgsGeometryCollection *>( testGeom )->geometryN( testPartIdx )->area();
154  else
155  val = testGeom->area();
156  }
157  if ( val > maxVal )
158  {
159  maxVal = val;
160  mergeFeature = testFeature;
161  mergePartIdx = testPartIdx;
162  }
163  }
164  else if ( method == MergeIdenticalAttribute )
165  {
166  if ( testFeature.attribute( mergeAttributeIndex ) == feature.attribute( mergeAttributeIndex ) )
167  {
168  mergeFeature = testFeature;
169  mergePartIdx = testPartIdx;
170  matchFound = true;
171  break;
172  }
173  }
174  }
175  }
176  if ( matchFound )
177  {
178  break;
179  }
180  }
181 
182  if ( !matchFound && maxVal == 0. )
183  {
184  return method == MergeIdenticalAttribute;
185  }
186 
187  // Merge geometries
188  const QgsGeometry mergeFeatureGeom = mergeFeature.geometry();
189  const QgsAbstractGeometry *mergeGeom = mergeFeatureGeom.constGet();
190  std::unique_ptr<QgsGeometryEngine> geomEngine( QgsGeometryCheckerUtils::createGeomEngine( QgsGeometryCheckerUtils::getGeomPart( mergeGeom, mergePartIdx ), mContext->reducedTolerance ) );
191  QgsAbstractGeometry *combinedGeom = geomEngine->combine( QgsGeometryCheckerUtils::getGeomPart( geom, partIdx ), &errMsg );
192  if ( !combinedGeom || combinedGeom->isEmpty() || !QgsWkbTypes::isSingleType( combinedGeom->wkbType() ) )
193  {
194  return false;
195  }
196 
197  // Replace polygon in merge geometry
198  if ( mergeFeature.id() == feature.id() && mergePartIdx > partIdx )
199  {
200  --mergePartIdx;
201  }
202  replaceFeatureGeometryPart( featurePools, layerId, mergeFeature, mergePartIdx, combinedGeom, changes );
203  // Remove polygon from source geometry
204  deleteFeatureGeometryPart( featurePools, layerId, feature, partIdx, changes );
205 
206  return true;
207 }
208 
210 {
211  static const QStringList methods = QStringList()
212  << tr( "Merge with neighboring polygon with longest shared edge" )
213  << tr( "Merge with neighboring polygon with largest area" )
214  << tr( "Merge with neighboring polygon with identical attribute value, if any, or leave as is" )
215  << tr( "Delete feature" )
216  << tr( "No action" );
217  return methods;
218 }
Abstract base class for all geometries.
virtual bool isEmpty() const
Returns true if the geometry is empty.
virtual int partCount() const =0
Returns count of parts contained in the geometry.
QgsWkbTypes::Type wkbType() const SIP_HOLDGIL
Returns the WKB type of the geometry.
virtual QgsPoint centroid() const
Returns the centroid of the geometry.
virtual double area() const
Returns the planar, 2-dimensional area of the geometry.
A feature pool is based on a vector layer and caches features.
QgsFeatureIds getIntersects(const QgsRectangle &rect) const
Gets all feature ids in the bounding box rect.
QgsVectorLayer * layer() const
Gets a pointer to the underlying layer.
bool getFeature(QgsFeatureId id, QgsFeature &feature)
Retrieves the feature with the specified id into feature.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
QgsGeometry geometry
Definition: qgsfeature.h:67
QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
Definition: qgsfeature.cpp:320
Q_GADGET QgsFeatureId id
Definition: qgsfeature.h:64
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition: qgsfeedback.h:45
Q_DECL_DEPRECATED QStringList resolutionMethods() const override
Returns a list of descriptions for available resolutions for errors.
void fixError(const QMap< QString, QgsFeaturePool * > &featurePools, QgsGeometryCheckError *error, int method, const QMap< QString, int > &mergeAttributeIndices, Changes &changes) const override
Fixes the error error with the specified method.
QList< QgsWkbTypes::GeometryType > compatibleGeometryTypes() const override
A list of geometry types for which this check can be performed.
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 double reducedTolerance
The tolerance to allow for in geometry checks.
This represents an error reported by a geometry check.
@ ValueArea
The value is an area.
const QString & layerId() const
The id of the layer on which this error has been detected.
const QgsVertexId & vidx() const
The id of the affected vertex.
QgsFeatureId featureId() const
The id of the feature on which this error has been detected.
void setFixed(int method)
Set the status to fixed and specify the method that has been used to fix the error.
void setFixFailed(const QString &reason)
Set the error status to failed and specify the reason for failure.
void setObsolete()
Set the error status to obsolete.
QMap< QString, QMap< QgsFeatureId, QList< QgsGeometryCheck::Change > > > Changes
A collection of changes.
void deleteFeatureGeometryPart(const QMap< QString, QgsFeaturePool * > &featurePools, const QString &layerId, QgsFeature &feature, int partIdx, Changes &changes) const
Deletes a part of a feature geometry.
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.
const QgsGeometryCheckContext * mContext
QMap< QString, QgsFeatureIds > allLayerFeatureIds(const QMap< QString, QgsFeaturePool * > &featurePools) const
Returns all layers and feature ids.
double scaleFactor(const QPointer< QgsVectorLayer > &layer) const
Determines the scale factor of a layer to the map coordinate reference system.
A layer feature combination to uniquely identify and access a feature in a set of layers.
Contains a set of layers and feature ids in those layers to pass to a geometry check.
static std::unique_ptr< QgsGeometryEngine > createGeomEngine(const QgsAbstractGeometry *geometry, double tolerance)
static QgsAbstractGeometry * getGeomPart(QgsAbstractGeometry *geom, int partIdx)
static double sharedEdgeLength(const QgsAbstractGeometry *geom1, const QgsAbstractGeometry *geom2, double tol)
Geometry collection.
double area() const override SIP_HOLDGIL
Returns the planar, 2-dimensional area of the geometry.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:125
const QgsAbstractGeometry * constGet() const SIP_HOLDGIL
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
static bool isSingleType(Type type) SIP_HOLDGIL
Returns true if the WKB type is a single type.
Definition: qgswkbtypes.h:852
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:37
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
Definition: qgsfeatureid.h:28
A list of layers and feature ids for each of these layers.
QMap< QString, QgsFeatureIds > toMap() const
Utility class for identifying a unique vertex within a geometry.
Definition: qgsvertexid.h:31
int part
Part number.
Definition: qgsvertexid.h:89
bool isValid() const SIP_HOLDGIL
Returns true if the vertex id is valid.
Definition: qgsvertexid.h:46