QGIS API Documentation 3.27.0-Master (9c08adf5ef)
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"
30
31
32
33QgsGeometryChecker::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
65QFuture<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
98void QgsGeometryChecker::emitProgressValue()
99{
100 emit progressValue( mFeedback.progress() );
101}
102
103bool QgsGeometryChecker::fixError( QgsGeometryCheckError *error, int method, bool triggerRepaint )
104{
105 mMessages.clear();
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 );
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
284void 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
300QgsGeometryChecker::RunCheckWrapper::RunCheckWrapper( QgsGeometryChecker *instance )
301 : mInstance( instance )
302{
303}
304
305void QgsGeometryChecker::RunCheckWrapper::operator()( const QgsGeometryCheck *check )
306{
307 mInstance->runCheck( mInstance->mFeaturePools, check );
308}
Class for doing transforms between two map coordinate systems.
void setBallparkTransformsAreAppropriate(bool appropriate)
Sets whether approximate "ballpark" results are appropriate for this coordinate transform.
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.
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.
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
double progress() const SIP_HOLDGIL
Returns the current progress reported by the feedback object.
Definition: qgsfeedback.h:80
Base configuration for geometry checks.
const QgsCoordinateReferenceSystem mapCrs
The coordinate system in which calculations should be done.
const double tolerance
The tolerance to allow for in geometry checks.
This represents an error reported by a geometry check.
Status
The status of an error.
@ StatusFixed
The error is fixed.
@ StatusObsolete
The error is obsolete because of other modifications.
Status status() const
The status of the error.
QString resolutionMessage() const
A message with details, how the error has been resolved.
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.
virtual void update(const QgsGeometryCheckError *other)
Update this error with the information from other.
virtual QString description() const
The error description.
QVariant value() const
An additional value for the error.
void setObsolete()
Set the error status to obsolete.
const QgsGeometryCheck * check() const
The geometry check that created this error.
const QString & layerId() const
The id of the layer on which this error has been detected.
virtual QgsRectangle affectedAreaBBox() const
The bounding box of the affected area of the error.
const QgsPointXY & location() const
The location of the error in map units.
This class implements a geometry check.
QMap< QString, QMap< QgsFeatureId, QList< QgsGeometryCheck::Change > > > Changes
A collection of changes.
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.
@ ChangeFeature
This change happens on feature level.
@ LayerCheck
The check controls a whole layer (topology checks)
@ FeatureCheck
The check controls geometries as a whole.
@ ChangeRemoved
Something has been removed.
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.
Manages and runs a set of geometry checks.
const QMap< QString, QgsFeaturePool * > featurePools() const
QFuture< void > execute(int *totalSteps=nullptr)
void progressValue(int value)
bool fixError(QgsGeometryCheckError *error, int method, bool triggerRepaint=false)
QgsGeometryChecker(const QList< QgsGeometryCheck * > &checks, QgsGeometryCheckContext *context, const QMap< QString, QgsFeaturePool * > &featurePools)
void errorAdded(QgsGeometryCheckError *error)
void errorUpdated(QgsGeometryCheckError *error, bool statusChanged)
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:79
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:480
A rectangle specified with double values.
Definition: qgsrectangle.h:42
void grow(double delta)
Grows the rectangle in place by the specified amount.
Definition: qgsrectangle.h:296
void combineExtentWith(const QgsRectangle &rect)
Expands the rectangle so that it covers both the original rectangle and the given rectangle.
Definition: qgsrectangle.h:391
bool contains(const QgsRectangle &rect) const SIP_HOLDGIL
Returns true when rectangle contains other rectangle.
Definition: qgsrectangle.h:363
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
Definition: qgsfeatureid.h:28
Descripts a change to fix a geometry.
int vertex
Vertex number.
Definition: qgsvertexid.h:95
int part
Part number.
Definition: qgsvertexid.h:89
int ring
Ring number.
Definition: qgsvertexid.h:92