@@ -21,10 +21,18 @@ email : matthias@opengis.ch
21
21
#include "qgsanalysis.h"
22
22
#include "qgsgeometrycheckregistry.h"
23
23
#include "qgsgeometrycheckfactory.h"
24
+ #include "qgsvectorlayereditbuffer.h"
25
+ #include "qgsvectorlayerfeaturepool.h"
26
+ #include "qgsfeedback.h"
27
+ #include "qgsreadwritelocker.h"
28
+
29
+ #include <QtConcurrent>
30
+ #include <QFutureWatcher>
24
31
25
32
QgsGeometryValidationService::QgsGeometryValidationService( QgsProject *project )
26
33
: mProject( project )
27
34
{
35
+ qRegisterMetaType< QList<std::shared_ptr<QgsGeometryCheckError> > >( "QList<std::shared_ptr<QgsGeometryCheckError>>" );
28
36
connect( project, &QgsProject::layersAdded, this, &QgsGeometryValidationService::onLayersAdded );
29
37
}
30
38
@@ -64,11 +72,21 @@ void QgsGeometryValidationService::onLayersAdded( const QList<QgsMapLayer *> &la
64
72
65
73
void QgsGeometryValidationService::onFeatureAdded( QgsVectorLayer *layer, QgsFeatureId fid )
66
74
{
75
+ if ( !mLayerCheckStates[layer].topologyChecks.empty() )
76
+ {
77
+ // TODO: Cancel topology checks
78
+ layer->setAllowCommit( false );
79
+ }
67
80
processFeature( layer, fid );
68
81
}
69
82
70
83
void QgsGeometryValidationService::onGeometryChanged( QgsVectorLayer *layer, QgsFeatureId fid, const QgsGeometry &geometry )
71
84
{
85
+ if ( !mLayerCheckStates[layer].topologyChecks.empty() )
86
+ {
87
+ // TODO: Cancel topology checks
88
+ layer->setAllowCommit( false );
89
+ }
72
90
Q_UNUSED( geometry )
73
91
74
92
cancelChecks( layer, fid );
@@ -77,21 +95,32 @@ void QgsGeometryValidationService::onGeometryChanged( QgsVectorLayer *layer, Qgs
77
95
78
96
void QgsGeometryValidationService::onFeatureDeleted( QgsVectorLayer *layer, QgsFeatureId fid )
79
97
{
98
+ if ( !mLayerCheckStates[layer].topologyChecks.empty() )
99
+ {
100
+ // TODO: Cancel topology checks
101
+ layer->setAllowCommit( false );
102
+ }
103
+
80
104
cancelChecks( layer, fid );
81
105
}
82
106
83
107
void QgsGeometryValidationService::onBeforeCommitChanges( QgsVectorLayer *layer )
84
108
{
85
- if ( !mTopologyChecksOk.value( layer ) )
109
+ if ( !mLayerCheckStates[layer].topologyChecks.empty( ) ) // TODO && topologyChecks not fulfilled
86
110
{
111
+ if ( !layer->allowCommit() )
112
+ {
113
+ emit warning( tr( "Can not save yet, we'll need to run some topology checks on your dataset first..." ) );
114
+ }
87
115
triggerTopologyChecks( layer );
88
116
}
89
117
}
90
118
91
119
void QgsGeometryValidationService::enableLayerChecks( QgsVectorLayer *layer )
92
120
{
93
121
// TODO: finish all ongoing checks
94
- qDeleteAll( mSingleFeatureChecks.value( layer ) );
122
+ qDeleteAll( mLayerCheckStates[layer].singleFeatureChecks );
123
+ qDeleteAll( mLayerCheckStates[layer].topologyChecks );
95
124
96
125
// TODO: ownership and lifetime of the context!!
97
126
auto context = new QgsGeometryCheckContext( 1, mProject->crs(), mProject->transformContext() );
@@ -120,16 +149,23 @@ void QgsGeometryValidationService::enableLayerChecks( QgsVectorLayer *layer )
120
149
singleGeometryChecks.append( dynamic_cast<QgsSingleGeometryCheck *>( check ) );
121
150
}
122
151
123
- mSingleFeatureChecks.insert( layer, singleGeometryChecks ) ;
152
+ mLayerCheckStates[layer].singleFeatureChecks = singleGeometryChecks;
124
153
125
- #if 0
154
+ // Topology checks
155
+ QList<QgsGeometryCheck *> topologyChecks;
126
156
const QList<QgsGeometryCheckFactory *> topologyCheckFactories = checkRegistry->geometryCheckFactories( layer, QgsGeometryCheck::SingleLayerTopologyCheck );
127
157
128
- for ( const QString &check : activeChecks )
158
+ for ( QgsGeometryCheckFactory *factory : topologyCheckFactories )
129
159
{
130
- checkRegistry->geometryCheckFactories( layer, QgsGeometryCheck::SingleLayerTopologyCheck );
160
+ const QString checkId = factory->id();
161
+ if ( activeChecks.contains( checkId ) )
162
+ {
163
+ const QVariantMap checkConfiguration = layer->geometryOptions()->checkConfiguration( checkId );
164
+ topologyChecks.append( factory->createGeometryCheck( context, checkConfiguration ) );
165
+ }
131
166
}
132
- #endif
167
+
168
+ mLayerCheckStates[layer].topologyChecks = topologyChecks;
133
169
}
134
170
135
171
void QgsGeometryValidationService::cancelChecks( QgsVectorLayer *layer, QgsFeatureId fid )
@@ -143,7 +179,7 @@ void QgsGeometryValidationService::processFeature( QgsVectorLayer *layer, QgsFea
143
179
144
180
QgsFeature feature = layer->getFeature( fid );
145
181
146
- const auto &checks = mSingleFeatureChecks.value( layer ) ;
182
+ const auto &checks = mLayerCheckStates[ layer].singleFeatureChecks ;
147
183
148
184
// The errors are going to be sent out via a signal. We cannot keep ownership in here (or can we?)
149
185
// nor can we be sure that a single slot is connected to the signal. So make it a shared_ptr.
@@ -161,5 +197,58 @@ void QgsGeometryValidationService::processFeature( QgsVectorLayer *layer, QgsFea
161
197
162
198
void QgsGeometryValidationService::triggerTopologyChecks( QgsVectorLayer *layer )
163
199
{
200
+ QFutureWatcher<void> *futureWatcher = mLayerCheckStates[layer].topologyCheckFutureWatcher;
201
+ if ( futureWatcher )
202
+ {
203
+ // TODO: kill!!
204
+ delete futureWatcher;
205
+ }
206
+
207
+ QgsFeatureIds checkFeatureIds = layer->editBuffer()->changedGeometries().keys().toSet();
208
+ checkFeatureIds.unite( layer->editBuffer()->addedFeatures().keys().toSet() );
209
+
210
+ // TODO: ownership of these objects...
211
+ QgsVectorLayerFeaturePool *featurePool = new QgsVectorLayerFeaturePool( layer );
212
+ QList<QgsGeometryCheckError *> &allErrors = mLayerCheckStates[layer].topologyCheckErrors;
213
+ QgsFeedback *feedback = new QgsFeedback();
214
+ QMap<QString, QgsFeatureIds> layerIds;
215
+ layerIds.insert( layer->id(), checkFeatureIds );
216
+ QgsGeometryCheck::LayerFeatureIds layerFeatureIds( layerIds );
217
+
218
+ QMap<QString, QgsFeaturePool *> featurePools;
219
+ featurePools.insert( layer->id(), featurePool );
220
+
221
+ const QList<QgsGeometryCheck *> checks = mLayerCheckStates[layer].topologyChecks;
222
+
223
+ QFuture<void> future = QtConcurrent::map( checks, [featurePools, &allErrors, feedback, layerFeatureIds, layer, this]( const QgsGeometryCheck * check )
224
+ {
225
+ // Watch out with the layer pointer in here. We are running in a thread, so we do not want to actually use it
226
+ // except for using its address to report the error.
227
+ QList<QgsGeometryCheckError *> errors;
228
+ QStringList messages; // Do we really need these?
229
+
230
+ check->collectErrors( featurePools, errors, messages, feedback, layerFeatureIds );
231
+ QgsReadWriteLocker errorLocker( mTopologyCheckLock, QgsReadWriteLocker::Write );
232
+ allErrors.append( errors );
233
+
234
+ QList<std::shared_ptr<QgsGeometryCheckError> > sharedErrors;
235
+ for ( QgsGeometryCheckError *error : errors )
236
+ {
237
+ sharedErrors.append( std::shared_ptr<QgsGeometryCheckError>( error ) );
238
+ }
239
+ emit topologyChecksUpdated( layer, sharedErrors );
240
+ errorLocker.unlock();
241
+ } );
242
+
243
+ futureWatcher = new QFutureWatcher<void>();
244
+ futureWatcher->setFuture( future );
245
+
246
+ connect( futureWatcher, &QFutureWatcherBase::finished, this, [&allErrors, layer, this]()
247
+ {
248
+ QgsReadWriteLocker errorLocker( mTopologyCheckLock, QgsReadWriteLocker::Read );
249
+ layer->setAllowCommit( allErrors.empty() );
250
+ errorLocker.unlock();
251
+ } );
164
252
253
+ mLayerCheckStates[layer].topologyCheckFutureWatcher = futureWatcher;
165
254
}
0 commit comments