Branch data Line data Source code
1 : : /***************************************************************************
2 : : * qgsgeometrychecker.cpp *
3 : : * ------------------- *
4 : : * copyright : (C) 2014 by Sandro Mani / Sourcepole AG *
5 : : * email : smani@sourcepole.ch *
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 : :
23 : : #include "qgsgeometrycheckcontext.h"
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 : 0 : QgsGeometryChecker::QgsGeometryChecker( const QList<QgsGeometryCheck *> &checks, QgsGeometryCheckContext *context, const QMap<QString, QgsFeaturePool *> &featurePools )
34 : 0 : : mChecks( checks )
35 : 0 : , mContext( context )
36 : 0 : , mFeaturePools( featurePools )
37 : 0 : {
38 : 0 : for ( auto it = featurePools.constBegin(); it != mFeaturePools.constEnd(); ++it )
39 : : {
40 : 0 : if ( it.value()->layer() )
41 : : {
42 : 0 : it.value()->layer()->setReadOnly( true );
43 : : // Enter update mode to defer ogr dataset repacking until the checker has finished
44 : 0 : it.value()->layer()->dataProvider()->enterUpdateMode();
45 : 0 : }
46 : 0 : }
47 : 0 : }
48 : :
49 : 0 : QgsGeometryChecker::~QgsGeometryChecker()
50 : 0 : {
51 : 0 : qDeleteAll( mCheckErrors );
52 : 0 : qDeleteAll( mChecks );
53 : 0 : for ( auto it = mFeaturePools.constBegin(); it != mFeaturePools.constEnd(); ++it )
54 : : {
55 : 0 : if ( it.value()->layer() )
56 : : {
57 : 0 : it.value()->layer()->dataProvider()->leaveUpdateMode();
58 : 0 : it.value()->layer()->setReadOnly( false );
59 : 0 : }
60 : 0 : delete it.value();
61 : 0 : }
62 : 0 : delete mContext;
63 : 0 : }
64 : :
65 : 0 : QFuture<void> QgsGeometryChecker::execute( int *totalSteps )
66 : : {
67 : 0 : if ( totalSteps )
68 : : {
69 : 0 : *totalSteps = 0;
70 : 0 : for ( QgsGeometryCheck *check : std::as_const( mChecks ) )
71 : : {
72 : 0 : for ( auto it = mFeaturePools.constBegin(); it != mFeaturePools.constEnd(); ++it )
73 : : {
74 : 0 : if ( check->checkType() <= QgsGeometryCheck::FeatureCheck )
75 : : {
76 : 0 : *totalSteps += check->isCompatible( it.value()->layer() ) ? it.value()->allFeatureIds().size() : 0;
77 : 0 : }
78 : : else
79 : : {
80 : 0 : *totalSteps += 1;
81 : : }
82 : 0 : }
83 : : }
84 : 0 : }
85 : :
86 : 0 : QFuture<void> future = QtConcurrent::map( mChecks, RunCheckWrapper( this ) );
87 : :
88 : 0 : QFutureWatcher<void> *watcher = new QFutureWatcher<void>();
89 : 0 : watcher->setFuture( future );
90 : 0 : QTimer *timer = new QTimer();
91 : 0 : connect( timer, &QTimer::timeout, this, &QgsGeometryChecker::emitProgressValue );
92 : 0 : connect( watcher, &QFutureWatcherBase::finished, timer, &QObject::deleteLater );
93 : 0 : connect( watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater );
94 : 0 : timer->start( 500 );
95 : :
96 : 0 : return future;
97 : 0 : }
98 : :
99 : 0 : void QgsGeometryChecker::emitProgressValue()
100 : : {
101 : 0 : emit progressValue( mFeedback.progress() );
102 : 0 : }
103 : :
104 : 0 : bool QgsGeometryChecker::fixError( QgsGeometryCheckError *error, int method, bool triggerRepaint )
105 : : {
106 : 0 : mMessages.clear();
107 : 0 : if ( error->status() >= QgsGeometryCheckError::StatusFixed )
108 : : {
109 : 0 : 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 : :
115 : 0 : QgsGeometryCheck::Changes changes;
116 : 0 : QgsRectangle recheckArea = error->affectedAreaBBox();
117 : :
118 : 0 : 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 : 0 : emit errorUpdated( error, true );
135 : 0 : if ( error->status() != QgsGeometryCheckError::StatusFixed )
136 : : {
137 : 0 : return false;
138 : : }
139 : :
140 : : // If nothing was changed, stop here
141 : 0 : if ( changes.isEmpty() )
142 : : {
143 : 0 : return true;
144 : : }
145 : :
146 : : // Determine what to recheck
147 : : // - Collect all features which were changed, get affected area
148 : 0 : QMap<QString, QSet<QgsFeatureId>> recheckFeatures;
149 : 0 : for ( auto it = changes.constBegin(); it != changes.constEnd(); ++it )
150 : : {
151 : 0 : const QMap<QgsFeatureId, QList<QgsGeometryCheck::Change>> &layerChanges = it.value();
152 : 0 : QgsFeaturePool *featurePool = mFeaturePools[it.key()];
153 : 0 : QgsCoordinateTransform t( featurePool->layer()->crs(), mContext->mapCrs, QgsProject::instance() );
154 : 0 : for ( auto layerChangeIt = layerChanges.constBegin(); layerChangeIt != layerChanges.constEnd(); ++layerChangeIt )
155 : : {
156 : 0 : bool removed = false;
157 : 0 : for ( const QgsGeometryCheck::Change &change : layerChangeIt.value() )
158 : : {
159 : 0 : if ( change.what == QgsGeometryCheck::ChangeFeature && change.type == QgsGeometryCheck::ChangeRemoved )
160 : : {
161 : 0 : removed = true;
162 : 0 : break;
163 : : }
164 : : }
165 : 0 : if ( !removed )
166 : : {
167 : 0 : QgsFeature f;
168 : 0 : if ( featurePool->getFeature( layerChangeIt.key(), f ) )
169 : : {
170 : 0 : recheckFeatures[it.key()].insert( layerChangeIt.key() );
171 : 0 : recheckArea.combineExtentWith( t.transformBoundingBox( f.geometry().boundingBox() ) );
172 : 0 : }
173 : 0 : }
174 : 0 : }
175 : 0 : }
176 : : // - Determine extent to recheck for gaps
177 : 0 : for ( QgsGeometryCheckError *err : std::as_const( mCheckErrors ) )
178 : : {
179 : 0 : if ( err->check()->checkType() == QgsGeometryCheck::LayerCheck )
180 : : {
181 : 0 : if ( err->affectedAreaBBox().intersects( recheckArea ) )
182 : : {
183 : 0 : recheckArea.combineExtentWith( err->affectedAreaBBox() );
184 : 0 : }
185 : 0 : }
186 : : }
187 : 0 : recheckArea.grow( 10 * mContext->tolerance );
188 : 0 : QMap<QString, QgsFeatureIds> recheckAreaFeatures;
189 : 0 : for ( const QString &layerId : mFeaturePools.keys() )
190 : : {
191 : 0 : QgsFeaturePool *featurePool = mFeaturePools[layerId];
192 : 0 : QgsCoordinateTransform t( mContext->mapCrs, featurePool->layer()->crs(), QgsProject::instance() );
193 : 0 : recheckAreaFeatures[layerId] = featurePool->getIntersects( t.transform( recheckArea ) );
194 : 0 : }
195 : :
196 : : // Recheck feature / changed area to detect new errors
197 : 0 : QList<QgsGeometryCheckError *> recheckErrors;
198 : 0 : for ( const QgsGeometryCheck *check : std::as_const( mChecks ) )
199 : : {
200 : 0 : if ( check->checkType() == QgsGeometryCheck::LayerCheck )
201 : : {
202 : 0 : if ( !recheckAreaFeatures.isEmpty() )
203 : : {
204 : 0 : check->collectErrors( mFeaturePools, recheckErrors, mMessages, nullptr, recheckAreaFeatures );
205 : 0 : }
206 : 0 : }
207 : : else
208 : : {
209 : 0 : if ( !recheckFeatures.isEmpty() )
210 : : {
211 : 0 : check->collectErrors( mFeaturePools, recheckErrors, mMessages, nullptr, recheckFeatures );
212 : 0 : }
213 : : }
214 : : }
215 : :
216 : : // Go through error list, update other errors of the checked feature
217 : 0 : for ( QgsGeometryCheckError *err : std::as_const( mCheckErrors ) )
218 : : {
219 : 0 : if ( err == error || err->status() == QgsGeometryCheckError::StatusObsolete )
220 : : {
221 : 0 : continue;
222 : : }
223 : :
224 : 0 : QgsGeometryCheckError::Status oldStatus = err->status();
225 : :
226 : 0 : bool handled = err->handleChanges( changes );
227 : :
228 : : // Check if this error now matches one found when rechecking the feature/area
229 : 0 : QgsGeometryCheckError *matchErr = nullptr;
230 : 0 : int nMatch = 0;
231 : 0 : for ( QgsGeometryCheckError *recheckErr : std::as_const( recheckErrors ) )
232 : : {
233 : 0 : if ( recheckErr->isEqual( err ) || recheckErr->closeMatch( err ) )
234 : : {
235 : 0 : ++nMatch;
236 : 0 : matchErr = recheckErr;
237 : 0 : }
238 : : }
239 : : // If just one close match was found, take it
240 : 0 : if ( nMatch == 1 && matchErr )
241 : : {
242 : 0 : err->update( matchErr );
243 : 0 : emit errorUpdated( err, err->status() != oldStatus );
244 : 0 : recheckErrors.removeAll( matchErr );
245 : 0 : delete matchErr;
246 : 0 : continue;
247 : : }
248 : :
249 : : // If no match is found and the error is not fixed or obsolete, set it to obsolete if...
250 : 0 : if ( err->status() < QgsGeometryCheckError::StatusFixed &&
251 : : (
252 : : // changes weren't handled
253 : 0 : !handled ||
254 : : // or if it is a FeatureNodeCheck or FeatureCheck error whose feature was rechecked
255 : 0 : ( err->check()->checkType() <= QgsGeometryCheck::FeatureCheck && recheckFeatures[err->layerId()].contains( err->featureId() ) ) ||
256 : : // or if it is a LayerCheck error within the rechecked area
257 : 0 : ( err->check()->checkType() == QgsGeometryCheck::LayerCheck && recheckArea.contains( err->affectedAreaBBox() ) )
258 : : )
259 : : )
260 : : {
261 : 0 : err->setObsolete();
262 : 0 : emit errorUpdated( err, err->status() != oldStatus );
263 : 0 : }
264 : : }
265 : :
266 : : // Add new errors
267 : 0 : for ( QgsGeometryCheckError *recheckErr : std::as_const( recheckErrors ) )
268 : : {
269 : 0 : emit errorAdded( recheckErr );
270 : 0 : mCheckErrors.append( recheckErr );
271 : : }
272 : :
273 : 0 : if ( triggerRepaint )
274 : : {
275 : 0 : for ( const QString &layerId : changes.keys() )
276 : : {
277 : 0 : mFeaturePools[layerId]->layer()->triggerRepaint();
278 : : }
279 : 0 : }
280 : :
281 : 0 : return true;
282 : 0 : }
283 : :
284 : 0 : void QgsGeometryChecker::runCheck( const QMap<QString, QgsFeaturePool *> &featurePools, const QgsGeometryCheck *check )
285 : : {
286 : : // Run checks
287 : 0 : QList<QgsGeometryCheckError *> errors;
288 : 0 : QStringList messages;
289 : 0 : check->collectErrors( featurePools, errors, messages, &mFeedback );
290 : 0 : mErrorListMutex.lock();
291 : 0 : mCheckErrors.append( errors );
292 : 0 : mMessages.append( messages );
293 : 0 : mErrorListMutex.unlock();
294 : 0 : for ( QgsGeometryCheckError *error : std::as_const( errors ) )
295 : : {
296 : 0 : emit errorAdded( error );
297 : : }
298 : 0 : }
299 : :
300 : 0 : QgsGeometryChecker::RunCheckWrapper::RunCheckWrapper( QgsGeometryChecker *instance )
301 : 0 : : mInstance( instance )
302 : : {
303 : 0 : }
304 : :
305 : 0 : void QgsGeometryChecker::RunCheckWrapper::operator()( const QgsGeometryCheck *check )
306 : : {
307 : 0 : mInstance->runCheck( mInstance->mFeaturePools, check );
308 : 0 : }
|