QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
qgsgeometrychecker.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  * qgsgeometrychecker.cpp *
3  * ------------------- *
4  * copyright : (C) 2014 by Sandro Mani / Sourcepole AG *
5  * email : [email protected] *
6  ***************************************************************************/
7 
8 /***************************************************************************
9  * *
10  * This program is free software; you can redistribute it and/or modify *
11  * it under the terms of the GNU General Public License as published by *
12  * the Free Software Foundation; either version 2 of the License, or *
13  * (at your option) any later version. *
14  * *
15  ***************************************************************************/
16 
17 #include <QtConcurrentMap>
18 #include <QFutureWatcher>
19 #include <QMutex>
20 #include <QTimer>
21 #include <QTextStream>
22 
24 #include "qgsgeometrychecker.h"
25 #include "qgsgeometrycheck.h"
26 #include "qgsfeaturepool.h"
27 #include "qgsproject.h"
28 #include "qgsvectorlayer.h"
29 #include "qgsgeometrycheckerror.h"
30 
31 
32 
33 QgsGeometryChecker::QgsGeometryChecker( const QList<QgsGeometryCheck *> &checks, QgsGeometryCheckContext *context, const QMap<QString, QgsFeaturePool *> &featurePools )
34  : mChecks( checks )
35  , mContext( context )
36  , mFeaturePools( featurePools )
37 {
38  for ( auto it = featurePools.constBegin(); it != mFeaturePools.constEnd(); ++it )
39  {
40  if ( it.value()->layer() )
41  {
42  it.value()->layer()->setReadOnly( true );
43  // Enter update mode to defer ogr dataset repacking until the checker has finished
44  it.value()->layer()->dataProvider()->enterUpdateMode();
45  }
46  }
47 }
48 
50 {
51  qDeleteAll( mCheckErrors );
52  qDeleteAll( mChecks );
53  for ( auto it = mFeaturePools.constBegin(); it != mFeaturePools.constEnd(); ++it )
54  {
55  if ( it.value()->layer() )
56  {
57  it.value()->layer()->dataProvider()->leaveUpdateMode();
58  it.value()->layer()->setReadOnly( false );
59  }
60  delete it.value();
61  }
62  delete mContext;
63 }
64 
65 QFuture<void> QgsGeometryChecker::execute( int *totalSteps )
66 {
67  if ( totalSteps )
68  {
69  *totalSteps = 0;
70  for ( QgsGeometryCheck *check : std::as_const( mChecks ) )
71  {
72  for ( auto it = mFeaturePools.constBegin(); it != mFeaturePools.constEnd(); ++it )
73  {
74  if ( check->checkType() <= QgsGeometryCheck::FeatureCheck )
75  {
76  *totalSteps += check->isCompatible( it.value()->layer() ) ? it.value()->allFeatureIds().size() : 0;
77  }
78  else
79  {
80  *totalSteps += 1;
81  }
82  }
83  }
84  }
85  QTimer *timer = new QTimer();
86  connect( timer, &QTimer::timeout, this, &QgsGeometryChecker::emitProgressValue );
87  QFutureWatcher<void> *watcher = new QFutureWatcher<void>();
88  connect( watcher, &QFutureWatcherBase::finished, timer, &QObject::deleteLater );
89  connect( watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater );
90 
91  QFuture<void> future = QtConcurrent::map( mChecks, RunCheckWrapper( this ) );
92  watcher->setFuture( future );
93  timer->start( 500 );
94 
95  return future;
96 }
97 
98 void QgsGeometryChecker::emitProgressValue()
99 {
100  emit progressValue( mFeedback.progress() );
101 }
102 
103 bool QgsGeometryChecker::fixError( QgsGeometryCheckError *error, int method, bool triggerRepaint )
104 {
105  mMessages.clear();
106  if ( error->status() >= QgsGeometryCheckError::StatusFixed )
107  {
108  return true;
109  }
110 #if 0
111  QTextStream( stdout ) << "Fixing " << error->description() << ": " << error->layerId() << ":" << error->featureId() << " @[" << error->vidx().part << ", " << error->vidx().ring << ", " << error->vidx().vertex << "](" << error->location().x() << ", " << error->location().y() << ") = " << error->value().toString() << endl;
112 #endif
113 
115  QgsRectangle recheckArea = error->affectedAreaBBox();
116 
117  error->check()->fixError( mFeaturePools, error, method, mMergeAttributeIndices, changes );
118 #if 0
119  QTextStream( stdout ) << " * Status: " << error->resolutionMessage() << endl;
120  static QVector<QString> strChangeWhat = { "ChangeFeature", "ChangePart", "ChangeRing", "ChangeNode" };
121  static QVector<QString> strChangeType = { "ChangeAdded", "ChangeRemoved", "ChangeChanged" };
122  for ( const QString &layerId : changes.keys() )
123  {
124  for ( const QgsFeatureId &fid : changes[layerId].keys() )
125  {
126  for ( const QgsGeometryCheck::Change &change : changes[layerId][fid] )
127  {
128  QTextStream( stdout ) << " * Change: " << layerId << ":" << fid << " :: " << strChangeWhat[change.what] << ":" << strChangeType[change.type] << ":(" << change.vidx.part << "," << change.vidx.ring << "," << change.vidx.vertex << ")" << endl;
129  }
130  }
131  }
132 #endif
133  emit errorUpdated( error, true );
134  if ( error->status() != QgsGeometryCheckError::StatusFixed )
135  {
136  return false;
137  }
138 
139  // If nothing was changed, stop here
140  if ( changes.isEmpty() )
141  {
142  return true;
143  }
144 
145  // Determine what to recheck
146  // - Collect all features which were changed, get affected area
147  QMap<QString, QSet<QgsFeatureId>> recheckFeatures;
148  for ( auto it = changes.constBegin(); it != changes.constEnd(); ++it )
149  {
150  const QMap<QgsFeatureId, QList<QgsGeometryCheck::Change>> &layerChanges = it.value();
151  QgsFeaturePool *featurePool = mFeaturePools[it.key()];
152  QgsCoordinateTransform t( featurePool->layer()->crs(), mContext->mapCrs, QgsProject::instance() );
154  for ( auto layerChangeIt = layerChanges.constBegin(); layerChangeIt != layerChanges.constEnd(); ++layerChangeIt )
155  {
156  bool removed = false;
157  for ( const QgsGeometryCheck::Change &change : layerChangeIt.value() )
158  {
159  if ( change.what == QgsGeometryCheck::ChangeFeature && change.type == QgsGeometryCheck::ChangeRemoved )
160  {
161  removed = true;
162  break;
163  }
164  }
165  if ( !removed )
166  {
167  QgsFeature f;
168  if ( featurePool->getFeature( layerChangeIt.key(), f ) )
169  {
170  recheckFeatures[it.key()].insert( layerChangeIt.key() );
172  }
173  }
174  }
175  }
176  // - Determine extent to recheck for gaps
177  for ( QgsGeometryCheckError *err : std::as_const( mCheckErrors ) )
178  {
179  if ( err->check()->checkType() == QgsGeometryCheck::LayerCheck )
180  {
181  if ( err->affectedAreaBBox().intersects( recheckArea ) )
182  {
183  recheckArea.combineExtentWith( err->affectedAreaBBox() );
184  }
185  }
186  }
187  recheckArea.grow( 10 * mContext->tolerance );
188  QMap<QString, QgsFeatureIds> recheckAreaFeatures;
189  for ( const QString &layerId : mFeaturePools.keys() )
190  {
191  QgsFeaturePool *featurePool = mFeaturePools[layerId];
192  QgsCoordinateTransform t( mContext->mapCrs, featurePool->layer()->crs(), QgsProject::instance() );
193  recheckAreaFeatures[layerId] = featurePool->getIntersects( t.transform( recheckArea ) );
194  }
195 
196  // Recheck feature / changed area to detect new errors
197  QList<QgsGeometryCheckError *> recheckErrors;
198  for ( const QgsGeometryCheck *check : std::as_const( mChecks ) )
199  {
200  if ( check->checkType() == QgsGeometryCheck::LayerCheck )
201  {
202  if ( !recheckAreaFeatures.isEmpty() )
203  {
204  check->collectErrors( mFeaturePools, recheckErrors, mMessages, nullptr, recheckAreaFeatures );
205  }
206  }
207  else
208  {
209  if ( !recheckFeatures.isEmpty() )
210  {
211  check->collectErrors( mFeaturePools, recheckErrors, mMessages, nullptr, recheckFeatures );
212  }
213  }
214  }
215 
216  // Go through error list, update other errors of the checked feature
217  for ( QgsGeometryCheckError *err : std::as_const( mCheckErrors ) )
218  {
219  if ( err == error || err->status() == QgsGeometryCheckError::StatusObsolete )
220  {
221  continue;
222  }
223 
224  QgsGeometryCheckError::Status oldStatus = err->status();
225 
226  bool handled = err->handleChanges( changes );
227 
228  // Check if this error now matches one found when rechecking the feature/area
229  QgsGeometryCheckError *matchErr = nullptr;
230  int nMatch = 0;
231  for ( QgsGeometryCheckError *recheckErr : std::as_const( recheckErrors ) )
232  {
233  if ( recheckErr->isEqual( err ) || recheckErr->closeMatch( err ) )
234  {
235  ++nMatch;
236  matchErr = recheckErr;
237  }
238  }
239  // If just one close match was found, take it
240  if ( nMatch == 1 && matchErr )
241  {
242  err->update( matchErr );
243  emit errorUpdated( err, err->status() != oldStatus );
244  recheckErrors.removeAll( matchErr );
245  delete matchErr;
246  continue;
247  }
248 
249  // If no match is found and the error is not fixed or obsolete, set it to obsolete if...
250  if ( err->status() < QgsGeometryCheckError::StatusFixed &&
251  (
252  // changes weren't handled
253  !handled ||
254  // or if it is a FeatureNodeCheck or FeatureCheck error whose feature was rechecked
255  ( err->check()->checkType() <= QgsGeometryCheck::FeatureCheck && recheckFeatures[err->layerId()].contains( err->featureId() ) ) ||
256  // or if it is a LayerCheck error within the rechecked area
257  ( err->check()->checkType() == QgsGeometryCheck::LayerCheck && recheckArea.contains( err->affectedAreaBBox() ) )
258  )
259  )
260  {
261  err->setObsolete();
262  emit errorUpdated( err, err->status() != oldStatus );
263  }
264  }
265 
266  // Add new errors
267  for ( QgsGeometryCheckError *recheckErr : std::as_const( recheckErrors ) )
268  {
269  emit errorAdded( recheckErr );
270  mCheckErrors.append( recheckErr );
271  }
272 
273  if ( triggerRepaint )
274  {
275  for ( const QString &layerId : changes.keys() )
276  {
277  mFeaturePools[layerId]->layer()->triggerRepaint();
278  }
279  }
280 
281  return true;
282 }
283 
284 void QgsGeometryChecker::runCheck( const QMap<QString, QgsFeaturePool *> &featurePools, const QgsGeometryCheck *check )
285 {
286  // Run checks
287  QList<QgsGeometryCheckError *> errors;
288  QStringList messages;
289  check->collectErrors( featurePools, errors, messages, &mFeedback );
290  mErrorListMutex.lock();
291  mCheckErrors.append( errors );
292  mMessages.append( messages );
293  mErrorListMutex.unlock();
294  for ( QgsGeometryCheckError *error : std::as_const( errors ) )
295  {
296  emit errorAdded( error );
297  }
298 }
299 
300 QgsGeometryChecker::RunCheckWrapper::RunCheckWrapper( QgsGeometryChecker *instance )
301  : mInstance( instance )
302 {
303 }
304 
305 void QgsGeometryChecker::RunCheckWrapper::operator()( const QgsGeometryCheck *check )
306 {
307  mInstance->runCheck( mInstance->mFeaturePools, check );
308 }
QgsVertexId::part
int part
Part number.
Definition: qgsvertexid.h:89
QgsGeometryCheckError::value
QVariant value() const
An additional value for the error.
Definition: qgsgeometrycheckerror.h:128
QgsMapLayer::crs
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:79
QgsGeometryCheck::Change
Descripts a change to fix a geometry.
Definition: qgsgeometrycheck.h:176
QgsVertexId::vertex
int vertex
Vertex number.
Definition: qgsvertexid.h:95
QgsPointXY::y
double y
Definition: qgspointxy.h:63
QgsGeometryChecker::featurePools
const QMap< QString, QgsFeaturePool * > featurePools() const
Definition: qgsgeometrychecker.h:59
QgsGeometryCheck::ChangeFeature
@ ChangeFeature
This change happens on feature level.
Definition: qgsgeometrycheck.h:131
QgsRectangle::combineExtentWith
void combineExtentWith(const QgsRectangle &rect)
Expands the rectangle so that it covers both the original rectangle and the given rectangle.
Definition: qgsrectangle.h:391
QgsGeometryCheckError::check
const QgsGeometryCheck * check() const
The geometry check that created this error.
Definition: qgsgeometrycheckerror.h:80
QgsGeometryCheckError::StatusFixed
@ StatusFixed
The error is fixed.
Definition: qgsgeometrycheckerror.h:46
QgsGeometryCheckError::setObsolete
void setObsolete()
Set the error status to obsolete.
Definition: qgsgeometrycheckerror.h:166
QgsGeometryCheckError::featureId
QgsFeatureId featureId() const
The id of the feature on which this error has been detected.
Definition: qgsgeometrycheckerror.h:90
QgsGeometryCheck::Changes
QMap< QString, QMap< QgsFeatureId, QList< QgsGeometryCheck::Change > > > Changes
A collection of changes.
Definition: qgsgeometrycheck.h:220
QgsCoordinateTransform::transformBoundingBox
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool handle180Crossover=false) const SIP_THROW(QgsCsException)
Transforms a rectangle from the source CRS to the destination CRS.
Definition: qgscoordinatetransform.cpp:560
QgsFeature::geometry
QgsGeometry geometry
Definition: qgsfeature.h:71
QgsGeometryCheck::LayerCheck
@ LayerCheck
The check controls a whole layer (topology checks)
Definition: qgsgeometrycheck.h:158
QgsProject::instance
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:480
QgsGeometryCheckError::resolutionMessage
QString resolutionMessage() const
A message with details, how the error has been resolved.
Definition: qgsgeometrycheckerror.h:150
qgsgeometrycheckerror.h
QgsGeometryCheckError::Status
Status
The status of an error.
Definition: qgsgeometrycheckerror.h:42
QgsFeaturePool::getFeature
bool getFeature(QgsFeatureId id, QgsFeature &feature)
Retrieves the feature with the specified id into feature.
Definition: qgsfeaturepool.cpp:41
QgsGeometryCheck::collectErrors
virtual void collectErrors(const QMap< QString, QgsFeaturePool * > &featurePools, QList< QgsGeometryCheckError * > &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids=QgsGeometryCheck::LayerFeatureIds()) const =0
The main worker method.
QgsRectangle
A rectangle specified with double values.
Definition: qgsrectangle.h:41
qgsfeaturepool.h
QgsGeometryCheck::ChangeRemoved
@ ChangeRemoved
Something has been removed.
Definition: qgsgeometrycheck.h:145
QgsGeometryCheck
This class implements a geometry check.
Definition: qgsgeometrycheck.h:91
QgsGeometryChecker::QgsGeometryChecker
QgsGeometryChecker(const QList< QgsGeometryCheck * > &checks, QgsGeometryCheckContext *context, const QMap< QString, QgsFeaturePool * > &featurePools)
Definition: qgsgeometrychecker.cpp:33
QgsGeometryCheck::fixError
virtual void fixError(const QMap< QString, QgsFeaturePool * > &featurePools, QgsGeometryCheckError *error, int method, const QMap< QString, int > &mergeAttributeIndices, Changes &changes) const
Fixes the error error with the specified method.
Definition: qgsgeometrycheck.cpp:50
QgsGeometryChecker
Manages and runs a set of geometry checks.
Definition: qgsgeometrychecker.h:47
qgsgeometrycheckcontext.h
qgsgeometrycheck.h
QgsGeometryCheckError::affectedAreaBBox
virtual QgsRectangle affectedAreaBBox() const
The bounding box of the affected area of the error.
Definition: qgsgeometrycheckerror.cpp:93
QgsCoordinateTransform::setBallparkTransformsAreAppropriate
void setBallparkTransformsAreAppropriate(bool appropriate)
Sets whether approximate "ballpark" results are appropriate for this coordinate transform.
Definition: qgscoordinatetransform.cpp:939
QgsGeometryCheckError::location
const QgsPointXY & location() const
The location of the error in map units.
Definition: qgsgeometrycheckerror.h:121
QgsRectangle::contains
bool contains(const QgsRectangle &rect) const SIP_HOLDGIL
Returns true when rectangle contains other rectangle.
Definition: qgsrectangle.h:363
QgsGeometryCheckContext
Base configuration for geometry checks.
Definition: qgsgeometrycheckcontext.h:31
QgsGeometryCheckError::description
virtual QString description() const
The error description.
Definition: qgsgeometrycheckerror.h:116
QgsGeometryCheckContext::mapCrs
const QgsCoordinateReferenceSystem mapCrs
The coordinate system in which calculations should be done.
Definition: qgsgeometrycheckcontext.h:74
QgsGeometryCheckError::status
Status status() const
The status of the error.
Definition: qgsgeometrycheckerror.h:145
qgsgeometrychecker.h
QgsGeometryChecker::execute
QFuture< void > execute(int *totalSteps=nullptr)
Definition: qgsgeometrychecker.cpp:65
QgsGeometryCheckError::vidx
const QgsVertexId & vidx() const
The id of the affected vertex.
Definition: qgsgeometrycheckerror.h:140
qgsvectorlayer.h
QgsFeaturePool::layer
QgsVectorLayer * layer() const
Gets a pointer to the underlying layer.
Definition: qgsfeaturepool.cpp:109
QgsGeometryChecker::progressValue
void progressValue(int value)
QgsGeometryChecker::~QgsGeometryChecker
~QgsGeometryChecker() override
Definition: qgsgeometrychecker.cpp:49
QgsGeometryChecker::errorUpdated
void errorUpdated(QgsGeometryCheckError *error, bool statusChanged)
QgsPointXY::x
double x
Definition: qgspointxy.h:62
QgsGeometryCheckError::update
virtual void update(const QgsGeometryCheckError *other)
Update this error with the information from other.
Definition: qgsgeometrycheckerror.cpp:207
QgsGeometryCheckContext::tolerance
const double tolerance
The tolerance to allow for in geometry checks.
Definition: qgsgeometrycheckcontext.h:61
QgsGeometryChecker::errorAdded
void errorAdded(QgsGeometryCheckError *error)
QgsVertexId::ring
int ring
Ring number.
Definition: qgsvertexid.h:92
QgsGeometry::boundingBox
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
Definition: qgsgeometry.cpp:1080
QgsGeometryCheckError::StatusObsolete
@ StatusObsolete
The error is obsolete because of other modifications.
Definition: qgsgeometrycheckerror.h:47
QgsFeature
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:55
QgsFeedback::progress
double progress() const SIP_HOLDGIL
Returns the current progress reported by the feedback object.
Definition: qgsfeedback.h:93
QgsRectangle::grow
void grow(double delta)
Grows the rectangle in place by the specified amount.
Definition: qgsrectangle.h:296
QgsGeometryCheckError::layerId
const QString & layerId() const
The id of the layer on which this error has been detected.
Definition: qgsgeometrycheckerror.h:85
QgsCoordinateTransform::transform
QgsPointXY transform(const QgsPointXY &point, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const SIP_THROW(QgsCsException)
Transform the point from the source CRS to the destination CRS.
Definition: qgscoordinatetransform.cpp:272
QgsGeometryCheck::FeatureCheck
@ FeatureCheck
The check controls geometries as a whole.
Definition: qgsgeometrycheck.h:157
QgsCoordinateTransform
Class for doing transforms between two map coordinate systems.
Definition: qgscoordinatetransform.h:57
QgsGeometryCheckError
This represents an error reported by a geometry check.
Definition: qgsgeometrycheckerror.h:35
QgsFeaturePool::getIntersects
QgsFeatureIds getIntersects(const QgsRectangle &rect) const
Gets all feature ids in the bounding box rect.
Definition: qgsfeaturepool.cpp:102
qgsproject.h
QgsFeaturePool
A feature pool is based on a vector layer and caches features.
Definition: qgsfeaturepool.h:37
QgsFeatureId
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
Definition: qgsfeatureid.h:28
QgsGeometryChecker::fixError
bool fixError(QgsGeometryCheckError *error, int method, bool triggerRepaint=false)
Definition: qgsgeometrychecker.cpp:103