QGIS API Documentation 3.41.0-Master (45a0abf3bec)
Loading...
Searching...
No Matches
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 "moc_qgsgeometrychecker.cpp"
26#include "qgsgeometrycheck.h"
27#include "qgsfeaturepool.h"
28#include "qgsproject.h"
29#include "qgsvectorlayer.h"
31
32
33
34QgsGeometryChecker::QgsGeometryChecker( const QList<QgsGeometryCheck *> &checks, QgsGeometryCheckContext *context, const QMap<QString, QgsFeaturePool *> &featurePools )
35 : mChecks( checks )
36 , mContext( context )
37 , mFeaturePools( featurePools )
38{
39 for ( auto it = featurePools.constBegin(); it != mFeaturePools.constEnd(); ++it )
40 {
41 if ( it.value()->layer() )
42 {
43 it.value()->layer()->setReadOnly( true );
44 // Enter update mode to defer ogr dataset repacking until the checker has finished
45 it.value()->layer()->dataProvider()->enterUpdateMode();
46 }
47 }
48}
49
51{
52 qDeleteAll( mCheckErrors );
53 qDeleteAll( mChecks );
54 for ( auto it = mFeaturePools.constBegin(); it != mFeaturePools.constEnd(); ++it )
55 {
56 if ( it.value()->layer() )
57 {
58 it.value()->layer()->dataProvider()->leaveUpdateMode();
59 it.value()->layer()->setReadOnly( false );
60 }
61 delete it.value();
62 }
63 delete mContext;
64}
65
66QFuture<void> QgsGeometryChecker::execute( int *totalSteps )
67{
68 if ( totalSteps )
69 {
70 *totalSteps = 0;
71 for ( QgsGeometryCheck *check : std::as_const( mChecks ) )
72 {
73 for ( auto it = mFeaturePools.constBegin(); it != mFeaturePools.constEnd(); ++it )
74 {
75 if ( check->checkType() <= QgsGeometryCheck::FeatureCheck )
76 {
77 *totalSteps += check->isCompatible( it.value()->layer() ) ? it.value()->allFeatureIds().size() : 0;
78 }
79 else
80 {
81 *totalSteps += 1;
82 }
83 }
84 }
85 }
86 QTimer *timer = new QTimer();
87 connect( timer, &QTimer::timeout, this, &QgsGeometryChecker::emitProgressValue );
88 QFutureWatcher<void> *watcher = new QFutureWatcher<void>();
89 connect( watcher, &QFutureWatcherBase::finished, timer, &QObject::deleteLater );
90 connect( watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater );
91
92 QFuture<void> future = QtConcurrent::map( mChecks, RunCheckWrapper( this ) );
93 watcher->setFuture( future );
94 timer->start( 500 );
95
96 return future;
97}
98
99void QgsGeometryChecker::emitProgressValue()
100{
101 emit progressValue( mFeedback.progress() );
102}
103
104bool QgsGeometryChecker::fixError( QgsGeometryCheckError *error, int method, bool triggerRepaint )
105{
106 mMessages.clear();
108 {
109 return true;
110 }
111#if 0
112 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;
113#endif
114
116 QgsRectangle recheckArea = error->affectedAreaBBox();
117
118 error->check()->fixError( mFeaturePools, error, method, mMergeAttributeIndices, changes );
119#if 0
120 QTextStream( stdout ) << " * Status: " << error->resolutionMessage() << endl;
121 static QVector<QString> strChangeWhat = { "ChangeFeature", "ChangePart", "ChangeRing", "ChangeNode" };
122 static QVector<QString> strChangeType = { "ChangeAdded", "ChangeRemoved", "ChangeChanged" };
123 for ( const QString &layerId : changes.keys() )
124 {
125 for ( const QgsFeatureId &fid : changes[layerId].keys() )
126 {
127 for ( const QgsGeometryCheck::Change &change : changes[layerId][fid] )
128 {
129 QTextStream( stdout ) << " * Change: " << layerId << ":" << fid << " :: " << strChangeWhat[change.what] << ":" << strChangeType[change.type] << ":(" << change.vidx.part << "," << change.vidx.ring << "," << change.vidx.vertex << ")" << endl;
130 }
131 }
132 }
133#endif
134 emit errorUpdated( error, true );
136 {
137 return false;
138 }
139
140 // If nothing was changed, stop here
141 if ( changes.isEmpty() )
142 {
143 return true;
144 }
145
146 // Determine what to recheck
147 // - Collect all features which were changed, get affected area
148 QMap<QString, QSet<QgsFeatureId>> recheckFeatures;
149 for ( auto it = changes.constBegin(); it != changes.constEnd(); ++it )
150 {
151 const QMap<QgsFeatureId, QList<QgsGeometryCheck::Change>> &layerChanges = it.value();
152 QgsFeaturePool *featurePool = mFeaturePools[it.key()];
153 QgsCoordinateTransform t( featurePool->crs(), mContext->mapCrs, QgsProject::instance() );
155 for ( auto layerChangeIt = layerChanges.constBegin(); layerChangeIt != layerChanges.constEnd(); ++layerChangeIt )
156 {
157 bool removed = false;
158 for ( const QgsGeometryCheck::Change &change : layerChangeIt.value() )
159 {
160 if ( change.what == QgsGeometryCheck::ChangeFeature && change.type == QgsGeometryCheck::ChangeRemoved )
161 {
162 removed = true;
163 break;
164 }
165 }
166 if ( !removed )
167 {
168 QgsFeature f;
169 if ( featurePool->getFeature( layerChangeIt.key(), f ) )
170 {
171 recheckFeatures[it.key()].insert( layerChangeIt.key() );
173 }
174 }
175 }
176 }
177 // - Determine extent to recheck for gaps
178 for ( QgsGeometryCheckError *err : std::as_const( mCheckErrors ) )
179 {
180 if ( err->check()->checkType() == QgsGeometryCheck::LayerCheck )
181 {
182 if ( err->affectedAreaBBox().intersects( recheckArea ) )
183 {
184 recheckArea.combineExtentWith( err->affectedAreaBBox() );
185 }
186 }
187 }
188 recheckArea.grow( 10 * mContext->tolerance );
189 QMap<QString, QgsFeatureIds> recheckAreaFeatures;
190 for ( auto it = mFeaturePools.constBegin(); it != mFeaturePools.constEnd(); it++ )
191 {
192 QgsFeaturePool *featurePool = it.value();
193 QgsCoordinateTransform t( mContext->mapCrs, featurePool->crs(), QgsProject::instance() );
194 recheckAreaFeatures[it.key()] = featurePool->getIntersects( t.transform( recheckArea ) );
195 }
196
197 // Recheck feature / changed area to detect new errors
198 QList<QgsGeometryCheckError *> recheckErrors;
199 for ( const QgsGeometryCheck *check : std::as_const( mChecks ) )
200 {
201 if ( check->checkType() == QgsGeometryCheck::LayerCheck )
202 {
203 if ( !recheckAreaFeatures.isEmpty() )
204 {
205 check->collectErrors( mFeaturePools, recheckErrors, mMessages, nullptr, recheckAreaFeatures );
206 }
207 }
208 else
209 {
210 if ( !recheckFeatures.isEmpty() )
211 {
212 check->collectErrors( mFeaturePools, recheckErrors, mMessages, nullptr, recheckFeatures );
213 }
214 }
215 }
216
217 // Go through error list, update other errors of the checked feature
218 for ( QgsGeometryCheckError *err : std::as_const( mCheckErrors ) )
219 {
220 if ( err == error || err->status() == QgsGeometryCheckError::StatusObsolete )
221 {
222 continue;
223 }
224
225 QgsGeometryCheckError::Status oldStatus = err->status();
226
227 bool handled = err->handleChanges( changes );
228
229 // Check if this error now matches one found when rechecking the feature/area
230 QgsGeometryCheckError *matchErr = nullptr;
231 int nMatch = 0;
232 for ( QgsGeometryCheckError *recheckErr : std::as_const( recheckErrors ) )
233 {
234 if ( recheckErr->isEqual( err ) || recheckErr->closeMatch( err ) )
235 {
236 ++nMatch;
237 matchErr = recheckErr;
238 }
239 }
240 // If just one close match was found, take it
241 if ( nMatch == 1 && matchErr )
242 {
243 err->update( matchErr );
244 emit errorUpdated( err, err->status() != oldStatus );
245 recheckErrors.removeAll( matchErr );
246 delete matchErr;
247 continue;
248 }
249
250 // If no match is found and the error is not fixed or obsolete, set it to obsolete if...
251 if ( err->status() < QgsGeometryCheckError::StatusFixed &&
252 (
253 // changes weren't handled
254 !handled ||
255 // or if it is a FeatureNodeCheck or FeatureCheck error whose feature was rechecked
256 ( err->check()->checkType() <= QgsGeometryCheck::FeatureCheck && recheckFeatures[err->layerId()].contains( err->featureId() ) ) ||
257 // or if it is a LayerCheck error within the rechecked area
258 ( err->check()->checkType() == QgsGeometryCheck::LayerCheck && recheckArea.contains( err->affectedAreaBBox() ) )
259 )
260 )
261 {
262 err->setObsolete();
263 emit errorUpdated( err, err->status() != oldStatus );
264 }
265 }
266
267 // Add new errors
268 for ( QgsGeometryCheckError *recheckErr : std::as_const( recheckErrors ) )
269 {
270 emit errorAdded( recheckErr );
271 mCheckErrors.append( recheckErr );
272 }
273
274 if ( triggerRepaint )
275 {
276 for ( auto itChange = changes.constBegin(); itChange != changes.constEnd(); itChange++ )
277 {
278 mFeaturePools[itChange.key()]->layer()->triggerRepaint();
279 }
280 }
281
282 return true;
283}
284
285void QgsGeometryChecker::runCheck( const QMap<QString, QgsFeaturePool *> &featurePools, const QgsGeometryCheck *check )
286{
287 // Run checks
288 QList<QgsGeometryCheckError *> errors;
289 QStringList messages;
290 check->collectErrors( featurePools, errors, messages, &mFeedback );
291 mErrorListMutex.lock();
292 mCheckErrors.append( errors );
293 mMessages.append( messages );
294 mErrorListMutex.unlock();
295 for ( QgsGeometryCheckError *error : std::as_const( errors ) )
296 {
297 emit errorAdded( error );
298 }
299}
300
301QgsGeometryChecker::RunCheckWrapper::RunCheckWrapper( QgsGeometryChecker *instance )
302 : mInstance( instance )
303{
304}
305
306void QgsGeometryChecker::RunCheckWrapper::operator()( const QgsGeometryCheck *check )
307{
308 mInstance->runCheck( mInstance->mFeaturePools, check );
309}
Class for doing transforms between two map coordinate systems.
void setBallparkTransformsAreAppropriate(bool appropriate)
Sets whether approximate "ballpark" results are appropriate for this coordinate transform.
QgsPointXY transform(const QgsPointXY &point, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transform the point from the source CRS to the destination CRS.
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool handle180Crossover=false) const
Transforms a rectangle from the source CRS to the destination CRS.
A feature pool is based on a vector layer and caches features.
QgsCoordinateReferenceSystem crs() const
The coordinate reference system of this layer.
QgsFeatureIds getIntersects(const QgsRectangle &rect) const
Gets all feature ids in the bounding box rect.
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:58
QgsGeometry geometry
Definition qgsfeature.h:69
double progress() const
Returns the current progress reported by the feedback object.
Definition qgsfeedback.h:77
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.
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)
const QMap< QString, QgsFeaturePool * > & featurePools() const
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
static QgsProject * instance()
Returns the QgsProject singleton instance.
A rectangle specified with double values.
bool contains(const QgsRectangle &rect) const
Returns true when rectangle contains other rectangle.
void grow(double delta)
Grows the rectangle in place by the specified amount.
void combineExtentWith(const QgsRectangle &rect)
Expands the rectangle so that it covers both the original rectangle and the given rectangle.
qint64 QgsFeatureId
Descripts a change to fix a geometry.
int vertex
Vertex number.
Definition qgsvertexid.h:94
int part
Part number.
Definition qgsvertexid.h:88
int ring
Ring number.
Definition qgsvertexid.h:91