Skip to content

Commit 9383b00

Browse files
authored
Merge pull request #9551 from m-kuhn/geom-validator-fixes
[geometry validation] Various bugfixes and improvements
2 parents d1910f5 + 6bcba42 commit 9383b00

28 files changed

+549
-304
lines changed

python/analysis/auto_generated/vector/geometry_checker/qgsfeaturepool.sip.in

+13-7
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010

1111

1212

13-
1413
class QgsFeaturePool : QgsFeatureSink /Abstract/
1514
{
1615
%Docstring
@@ -30,12 +29,11 @@ A feature pool is based on a vector layer and caches features.
3029
QgsFeaturePool( QgsVectorLayer *layer );
3130
virtual ~QgsFeaturePool();
3231

33-
bool getFeature( QgsFeatureId id, QgsFeature &feature, QgsFeedback *feedback = 0 );
32+
bool getFeature( QgsFeatureId id, QgsFeature &feature );
3433
%Docstring
35-
Retrieve the feature with the specified ``id`` into ``feature``.
36-
It will be retrieved from the cache or from the underlying layer if unavailable.
37-
If the feature is neither available from the cache nor from the layer it will return false.
38-
If ``feedback`` is specified, the call may return if the feedback is canceled.
34+
Retrieves the feature with the specified ``id`` into ``feature``.
35+
It will be retrieved from the cache or from the underlying feature source if unavailable.
36+
If the feature is neither available from the cache nor from the source it will return FALSE.
3937
%End
4038

4139

@@ -74,11 +72,19 @@ The geometry type of this layer.
7472
QgsCoordinateReferenceSystem crs() const;
7573
%Docstring
7674
The coordinate reference system of this layer.
75+
%End
76+
77+
QString layerName() const;
78+
%Docstring
79+
Returns the name of the layer.
80+
81+
Should be preferred over layer().name() because it can directly be run on
82+
the background thread.
7783
%End
7884

7985
protected:
8086

81-
void insertFeature( const QgsFeature &feature );
87+
void insertFeature( const QgsFeature &feature, bool skipLock = false );
8288
%Docstring
8389
Inserts a feature into the cache and the spatial index.
8490
To be used by implementations of ``addFeature``.

python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheckerror.sip.in

+1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ Will be used to update existing errors whenever they are re-checked.
7676
%End
7777

7878

79+
7980
protected:
8081
QgsGeometryCheckError( const QgsGeometryCheck *check,
8182
const QString &layerId,

python/analysis/auto_generated/vector/geometry_checker/qgsgeometrycheckerutils.sip.in

+6-2
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ If ``useMapCrs`` is True, geometries will be reprojected to the mapCrs defined
4444
in ``context``.
4545
%End
4646

47-
const QgsFeature &feature() const;
47+
QgsFeature feature() const;
4848
%Docstring
4949
Returns the feature.
5050
The geometry will not be reprojected regardless of useMapCrs.
@@ -56,13 +56,17 @@ The geometry will not be reprojected regardless of useMapCrs.
5656
The layer id.
5757
%End
5858

59-
const QgsGeometry &geometry() const;
59+
QgsGeometry geometry() const;
6060
%Docstring
6161
Returns the geometry of this feature.
6262
If useMapCrs was specified, it will already be reprojected into the
6363
CRS specified in the context specified in the constructor.
6464
%End
65+
6566
QString id() const;
67+
%Docstring
68+
Returns a combination of the layerId and the feature id.
69+
%End
6670
bool operator==( const QgsGeometryCheckerUtils::LayerFeature &other ) const;
6771
bool operator!=( const QgsGeometryCheckerUtils::LayerFeature &other ) const;
6872

resources/qgis_global_settings.ini

+8
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,11 @@ PostgreSQL\default_timeout=30
8383
# cause performance issues, as the records must all be loaded from the related table on display.
8484
maxEntriesRelationWidget=100
8585

86+
[geometry_validation]
87+
# A comma separated list of geometry validations to enable by default for newly added layers
88+
# Available checks: QgsIsValidCheck,QgsGeometryGapCheck,QgsGeometryOverlapCheck,QgsGeometryMissingVertexCheck
89+
default_checks=
90+
91+
# Enable problem resolution for geometry errors
92+
# This feature is experimental and has known issues.
93+
enable_problem_resolution=false

src/analysis/vector/geometry_checker/qgsfeaturepool.cpp

+28-13
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,14 @@
2929
QgsFeaturePool::QgsFeaturePool( QgsVectorLayer *layer )
3030
: mFeatureCache( CACHE_SIZE )
3131
, mLayer( layer )
32-
, mLayerId( layer->id() )
3332
, mGeometryType( layer->geometryType() )
34-
, mCrs( layer->crs() )
33+
, mFeatureSource( qgis::make_unique<QgsVectorLayerFeatureSource>( layer ) )
34+
, mLayerName( layer->name() )
3535
{
3636

3737
}
3838

39-
bool QgsFeaturePool::getFeature( QgsFeatureId id, QgsFeature &feature, QgsFeedback *feedback )
39+
bool QgsFeaturePool::getFeature( QgsFeatureId id, QgsFeature &feature )
4040
{
4141
// Why is there a write lock acquired here? Weird, we only want to read a feature from the cache, right?
4242
// A method like `QCache::object(const Key &key) const` certainly would not modify its internals.
@@ -55,11 +55,9 @@ bool QgsFeaturePool::getFeature( QgsFeatureId id, QgsFeature &feature, QgsFeedba
5555
}
5656
else
5757
{
58-
std::unique_ptr<QgsVectorLayerFeatureSource> source = QgsVectorLayerUtils::getFeatureSource( mLayer, feedback );
59-
6058
// Feature not in cache, retrieve from layer
6159
// TODO: avoid always querying all attributes (attribute values are needed when merging by attribute)
62-
if ( !source || !source->getFeatures( QgsFeatureRequest( id ) ).nextFeature( feature ) )
60+
if ( !mFeatureSource->getFeatures( QgsFeatureRequest( id ) ).nextFeature( feature ) )
6361
{
6462
return false;
6563
}
@@ -72,15 +70,22 @@ bool QgsFeaturePool::getFeature( QgsFeatureId id, QgsFeature &feature, QgsFeedba
7270

7371
QgsFeatureIds QgsFeaturePool::getFeatures( const QgsFeatureRequest &request, QgsFeedback *feedback )
7472
{
73+
QgsReadWriteLocker( mCacheLock, QgsReadWriteLocker::Write );
74+
Q_UNUSED( feedback )
75+
Q_ASSERT( QThread::currentThread() == qApp->thread() );
76+
77+
mFeatureCache.clear();
78+
mIndex = QgsSpatialIndex();
79+
7580
QgsFeatureIds fids;
7681

77-
std::unique_ptr<QgsVectorLayerFeatureSource> source = QgsVectorLayerUtils::getFeatureSource( mLayer, feedback );
82+
mFeatureSource = qgis::make_unique<QgsVectorLayerFeatureSource>( mLayer );
7883

79-
QgsFeatureIterator it = source->getFeatures( request );
84+
QgsFeatureIterator it = mFeatureSource->getFeatures( request );
8085
QgsFeature feature;
8186
while ( it.nextFeature( feature ) )
8287
{
83-
insertFeature( feature );
88+
insertFeature( feature, true );
8489
fids << feature.id();
8590
}
8691

@@ -111,9 +116,11 @@ QPointer<QgsVectorLayer> QgsFeaturePool::layerPtr() const
111116
return mLayer;
112117
}
113118

114-
void QgsFeaturePool::insertFeature( const QgsFeature &feature )
119+
void QgsFeaturePool::insertFeature( const QgsFeature &feature, bool skipLock )
115120
{
116-
QgsReadWriteLocker locker( mCacheLock, QgsReadWriteLocker::Write );
121+
QgsReadWriteLocker locker( mCacheLock, QgsReadWriteLocker::Unlocked );
122+
if ( !skipLock )
123+
locker.changeMode( QgsReadWriteLocker::Write );
117124
mFeatureCache.insert( feature.id(), new QgsFeature( feature ) );
118125
QgsFeature indexFeature( feature );
119126
mIndex.addFeature( indexFeature );
@@ -150,12 +157,19 @@ void QgsFeaturePool::setFeatureIds( const QgsFeatureIds &ids )
150157

151158
bool QgsFeaturePool::isFeatureCached( QgsFeatureId fid )
152159
{
160+
QgsReadWriteLocker locker( mCacheLock, QgsReadWriteLocker::Read );
153161
return mFeatureCache.contains( fid );
154162
}
155163

164+
QString QgsFeaturePool::layerName() const
165+
{
166+
return mLayerName;
167+
}
168+
156169
QgsCoordinateReferenceSystem QgsFeaturePool::crs() const
157170
{
158-
return mCrs;
171+
QgsReadWriteLocker( mCacheLock, QgsReadWriteLocker::Read );
172+
return mFeatureSource->crs();
159173
}
160174

161175
QgsWkbTypes::GeometryType QgsFeaturePool::geometryType() const
@@ -165,5 +179,6 @@ QgsWkbTypes::GeometryType QgsFeaturePool::geometryType() const
165179

166180
QString QgsFeaturePool::layerId() const
167181
{
168-
return mLayerId;
182+
QgsReadWriteLocker( mCacheLock, QgsReadWriteLocker::Read );
183+
return mFeatureSource->id();
169184
}

src/analysis/vector/geometry_checker/qgsfeaturepool.h

+19-11
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@
2525
#include "qgsfeature.h"
2626
#include "qgsspatialindex.h"
2727
#include "qgsfeaturesink.h"
28-
29-
class QgsVectorLayer;
28+
#include "qgsvectorlayerfeatureiterator.h"
3029

3130
/**
3231
* \ingroup analysis
@@ -43,19 +42,20 @@ class ANALYSIS_EXPORT QgsFeaturePool : public QgsFeatureSink SIP_ABSTRACT
4342
virtual ~QgsFeaturePool() = default;
4443

4544
/**
46-
* Retrieve the feature with the specified \a id into \a feature.
47-
* It will be retrieved from the cache or from the underlying layer if unavailable.
48-
* If the feature is neither available from the cache nor from the layer it will return false.
49-
* If \a feedback is specified, the call may return if the feedback is canceled.
45+
* Retrieves the feature with the specified \a id into \a feature.
46+
* It will be retrieved from the cache or from the underlying feature source if unavailable.
47+
* If the feature is neither available from the cache nor from the source it will return FALSE.
5048
*/
51-
bool getFeature( QgsFeatureId id, QgsFeature &feature, QgsFeedback *feedback = nullptr );
49+
bool getFeature( QgsFeatureId id, QgsFeature &feature );
5250

5351
/**
5452
* Get features for the provided \a request. No features will be fetched
5553
* from the cache and the request is sent directly to the underlying feature source.
5654
* Results of the request are cached in the pool and the ids of all the features
57-
* are returned. This can be used to warm the cache for a particular area of interest
55+
* are returned. This is used to warm the cache for a particular area of interest
5856
* (bounding box) or other set of features.
57+
* This will get a new feature source from the source vector layer.
58+
* This needs to be called from the main thread.
5959
* If \a feedback is specified, the call may return if the feedback is canceled.
6060
*/
6161
QgsFeatureIds getFeatures( const QgsFeatureRequest &request, QgsFeedback *feedback = nullptr ) SIP_SKIP;
@@ -121,13 +121,21 @@ class ANALYSIS_EXPORT QgsFeaturePool : public QgsFeatureSink SIP_ABSTRACT
121121
*/
122122
QgsCoordinateReferenceSystem crs() const;
123123

124+
/**
125+
* Returns the name of the layer.
126+
*
127+
* Should be preferred over layer().name() because it can directly be run on
128+
* the background thread.
129+
*/
130+
QString layerName() const;
131+
124132
protected:
125133

126134
/**
127135
* Inserts a feature into the cache and the spatial index.
128136
* To be used by implementations of ``addFeature``.
129137
*/
130-
void insertFeature( const QgsFeature &feature );
138+
void insertFeature( const QgsFeature &feature, bool skipLock = false );
131139

132140
/**
133141
* Changes a feature in the cache and the spatial index.
@@ -170,9 +178,9 @@ class ANALYSIS_EXPORT QgsFeaturePool : public QgsFeatureSink SIP_ABSTRACT
170178
mutable QReadWriteLock mCacheLock;
171179
QgsFeatureIds mFeatureIds;
172180
QgsSpatialIndex mIndex;
173-
QString mLayerId;
174181
QgsWkbTypes::GeometryType mGeometryType;
175-
QgsCoordinateReferenceSystem mCrs;
182+
std::unique_ptr<QgsVectorLayerFeatureSource> mFeatureSource;
183+
QString mLayerName;
176184
};
177185

178186
#endif // QGS_FEATUREPOOL_H

src/analysis/vector/geometry_checker/qgsgeometrycheckerror.cpp

+7-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ QgsGeometryCheckError::QgsGeometryCheckError( const QgsGeometryCheck *check,
5353
{
5454
if ( vidx.part != -1 )
5555
{
56-
mGeometry = QgsGeometry( QgsGeometryCheckerUtils::getGeomPart( layerFeature.geometry().constGet(), vidx.part )->clone() );
56+
const QgsGeometry geom = layerFeature.geometry();
57+
mGeometry = QgsGeometry( QgsGeometryCheckerUtils::getGeomPart( geom.constGet(), vidx.part )->clone() );
5758
}
5859
else
5960
{
@@ -180,6 +181,11 @@ bool QgsGeometryCheckError::handleChanges( const QgsGeometryCheck::Changes &chan
180181
return true;
181182
}
182183

184+
QMap<QString, QgsFeatureIds> QgsGeometryCheckError::involvedFeatures() const
185+
{
186+
return QMap<QString, QSet<QgsFeatureId> >();
187+
}
188+
183189
void QgsGeometryCheckError::update( const QgsGeometryCheckError *other )
184190
{
185191
Q_ASSERT( mCheck == other->mCheck );

src/analysis/vector/geometry_checker/qgsgeometrycheckerror.h

+9
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,15 @@ class ANALYSIS_EXPORT QgsGeometryCheckError
9393
*/
9494
virtual bool handleChanges( const QgsGeometryCheck::Changes &changes ) SIP_SKIP;
9595

96+
/**
97+
* Returns a list of involved features.
98+
* By default returns an empty map.
99+
* The map keys are layer ids, the map value is a set of feature ids.
100+
*
101+
* \since QGIS 3.8
102+
*/
103+
virtual QMap<QString, QgsFeatureIds > involvedFeatures() const SIP_SKIP;
104+
96105
protected:
97106
// Users of this constructor must ensure geometry and errorLocation are in map coordinates
98107
QgsGeometryCheckError( const QgsGeometryCheck *check,

src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.cpp

+6-6
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ QgsGeometryCheckerUtils::LayerFeature::LayerFeature( const QgsFeaturePool *pool,
5252
}
5353
}
5454

55-
const QgsFeature &QgsGeometryCheckerUtils::LayerFeature::feature() const
55+
QgsFeature QgsGeometryCheckerUtils::LayerFeature::feature() const
5656
{
5757
return mFeature;
5858
}
@@ -67,24 +67,24 @@ QString QgsGeometryCheckerUtils::LayerFeature::layerId() const
6767
return mFeaturePool->layerId();
6868
}
6969

70-
const QgsGeometry &QgsGeometryCheckerUtils::LayerFeature::geometry() const
70+
QgsGeometry QgsGeometryCheckerUtils::LayerFeature::geometry() const
7171
{
7272
return mGeometry;
7373
}
7474

7575
QString QgsGeometryCheckerUtils::LayerFeature::id() const
7676
{
77-
return QStringLiteral( "%1:%2" ).arg( layer()->name() ).arg( mFeature.id() );
77+
return QStringLiteral( "%1:%2" ).arg( mFeaturePool->layerName() ).arg( mFeature.id() );
7878
}
7979

8080
bool QgsGeometryCheckerUtils::LayerFeature::operator==( const LayerFeature &other ) const
8181
{
82-
return layer()->id() == other.layer()->id() && feature().id() == other.feature().id();
82+
return layerId() == other.layerId() && mFeature.id() == other.mFeature.id();
8383
}
8484

8585
bool QgsGeometryCheckerUtils::LayerFeature::operator!=( const LayerFeature &other ) const
8686
{
87-
return layer()->id() != other.layer()->id() || feature().id() != other.feature().id();
87+
return layerId() != other.layerId() || mFeature.id() != other.mFeature.id();
8888
}
8989

9090
/////////////////////////////////////////////////////////////////////////////
@@ -197,7 +197,7 @@ bool QgsGeometryCheckerUtils::LayerFeatures::iterator::nextFeature( bool begin )
197197
QgsFeature feature;
198198
if ( featurePool->getFeature( *mFeatureIt, feature ) && !feature.geometry().isNull() )
199199
{
200-
mCurrentFeature.reset( new LayerFeature( mParent->mFeaturePools[*mLayerIt], feature, mParent->mContext, mParent->mUseMapCrs ) );
200+
mCurrentFeature = qgis::make_unique<LayerFeature>( featurePool, feature, mParent->mContext, mParent->mUseMapCrs );
201201
return true;
202202
}
203203
++mFeatureIt;

src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.h

+6-2
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class ANALYSIS_EXPORT QgsGeometryCheckerUtils
5656
* Returns the feature.
5757
* The geometry will not be reprojected regardless of useMapCrs.
5858
*/
59-
const QgsFeature &feature() const;
59+
QgsFeature feature() const;
6060

6161
/**
6262
* The layer.
@@ -73,7 +73,11 @@ class ANALYSIS_EXPORT QgsGeometryCheckerUtils
7373
* If useMapCrs was specified, it will already be reprojected into the
7474
* CRS specified in the context specified in the constructor.
7575
*/
76-
const QgsGeometry &geometry() const;
76+
QgsGeometry geometry() const;
77+
78+
/**
79+
* Returns a combination of the layerId and the feature id.
80+
*/
7781
QString id() const;
7882
bool operator==( const QgsGeometryCheckerUtils::LayerFeature &other ) const;
7983
bool operator!=( const QgsGeometryCheckerUtils::LayerFeature &other ) const;

0 commit comments

Comments
 (0)