Merge pull request #8103 from m-kuhn/geometryValidatorCode_1

Geometry validation of editing session
This commit is contained in:
Matthias Kuhn 2018-10-16 11:34:50 +02:00 committed by GitHub
commit 190f938654
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
70 changed files with 2697 additions and 262 deletions

View File

@ -15,6 +15,12 @@ class QgsFeaturePool : QgsFeatureSink /Abstract/
{ {
%Docstring %Docstring
A feature pool is based on a vector layer and caches features. A feature pool is based on a vector layer and caches features.
.. note::
This class is a technology preview and unstable API.
.. versionadded:: 3.4
%End %End
%TypeHeaderCode %TypeHeaderCode
@ -31,6 +37,7 @@ It will be retrieved from the cache or from the underlying layer if unavailable.
If the feature is neither available from the cache nor from the layer it will return false. If the feature is neither available from the cache nor from the layer it will return false.
%End %End
virtual void updateFeature( QgsFeature &feature ) = 0; virtual void updateFeature( QgsFeature &feature ) = 0;
%Docstring %Docstring
Updates a feature in this pool. Updates a feature in this pool.
@ -89,6 +96,7 @@ To be used by implementations of ``deleteFeature``.
%End %End
private: private:
QgsFeaturePool( const QgsFeaturePool &other ); QgsFeaturePool( const QgsFeaturePool &other );
}; };

View File

@ -10,24 +10,19 @@
class QgsGeometryCheck class QgsGeometryCheck
{ {
%Docstring %Docstring
************************************************************************* This class manages all known geometry check factories.
qgsgeometrycheck.h
---------------------
begin : September 2014
copyright : (C) 2014 by Sandro Mani / Sourcepole AG
email : smani at sourcepole dot ch
**************************************************************************
This program is free software; you can redistribute it and/or modify * QgsGeometryCheckRegistry is not usually directly created, but rather accessed through
it under the terms of the GNU General Public License as published by * :py:func:`QgsAnalysis.geometryCheckRegistry()`
the Free Software Foundation; either version 2 of the License, or *
(at your option) any later version. *
************************************************************************** .. note::
This class is a technology preview and unstable API.
.. versionadded:: 3.4
%End %End
%TypeHeaderCode %TypeHeaderCode
@ -120,7 +115,7 @@ A list of geometry types for which this check can be performed.
Flags for this geometry check. Flags for this geometry check.
%End %End
virtual void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors /In,Out/, QStringList &messages /In,Out/, QgsFeedback *feedback = 0, const LayerFeatureIds &ids = QgsGeometryCheck::LayerFeatureIds() ) const = 0; virtual void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors /In,Out/, QStringList &messages /In,Out/, QgsFeedback *feedback, const LayerFeatureIds &ids = QgsGeometryCheck::LayerFeatureIds() ) const = 0;
%Docstring %Docstring
The main worker method. The main worker method.
Check all features available from ``featurePools`` and write errors found to ``errors``. Check all features available from ``featurePools`` and write errors found to ``errors``.

View File

@ -11,18 +11,16 @@
class QgsGeometryCheckError class QgsGeometryCheckError
{ {
%Docstring %Docstring
************************************************************************* This represents an error reported by a geometry check.
This program is free software; you can redistribute it and/or modify * .. note::
it under the terms of the GNU General Public License as published by *
the Free Software Foundation; either version 2 of the License, or *
(at your option) any later version. *
************************************************************************** This class is a technology preview and unstable API.
.. versionadded:: 3.4
%End %End
%TypeHeaderCode %TypeHeaderCode

View File

@ -10,17 +10,18 @@
class QgsGeometryCheckerUtils class QgsGeometryCheckerUtils
{ {
%Docstring %Docstring
*************************************************************************
This program is free software; you can redistribute it and/or modify * Contains utilities required for geometry checks.
it under the terms of the GNU General Public License as published by *
the Free Software Foundation; either version 2 of the License, or *
(at your option) any later version. *
************************************************************************** .. note::
This class is a technology preview and unstable API.
.. versionadded:: 3.4
%End %End
%TypeHeaderCode %TypeHeaderCode

View File

@ -14,6 +14,16 @@
class QgsGeometryCheckFactory /Abstract/ class QgsGeometryCheckFactory /Abstract/
{ {
%Docstring
A factory for geometry checks.
.. note::
This class is a technology preview and unstable API.
.. versionadded:: 3.4
%End
%TypeHeaderCode %TypeHeaderCode
#include "qgsgeometrycheckfactory.h" #include "qgsgeometrycheckfactory.h"

View File

@ -19,6 +19,10 @@ This class manages all known geometry check factories.
QgsGeometryCheckRegistry is not usually directly created, but rather accessed through QgsGeometryCheckRegistry is not usually directly created, but rather accessed through
:py:func:`QgsAnalysis.geometryCheckRegistry()` :py:func:`QgsAnalysis.geometryCheckRegistry()`
.. note::
This class is a technology preview and unstable API.
.. versionadded:: 3.4 .. versionadded:: 3.4
%End %End

View File

@ -17,6 +17,10 @@ class QgsSingleGeometryCheckError
An error from a QgsSingleGeometryCheck. An error from a QgsSingleGeometryCheck.
.. note::
This class is a technology preview and unstable API.
.. versionadded:: 3.4 .. versionadded:: 3.4
%End %End
@ -78,6 +82,10 @@ class QgsGeometryCheckErrorSingle : QgsGeometryCheckError
Wraps a QgsSingleGeometryError into a standard :py:class:`QgsGeometryCheckError`. Wraps a QgsSingleGeometryError into a standard :py:class:`QgsGeometryCheckError`.
The single error can be obtained via singleError. The single error can be obtained via singleError.
.. note::
This class is a technology preview and unstable API.
.. versionadded:: 3.4 .. versionadded:: 3.4
%End %End

View File

@ -9,7 +9,8 @@
class QgsGeometryOptions
class QgsGeometryOptions : QObject
{ {
%Docstring %Docstring
@ -72,6 +73,34 @@ Determines if at least one fix is enabled.
%Docstring %Docstring
Apply any fixes configured on this class to ``geometry``. Apply any fixes configured on this class to ``geometry``.
.. versionadded:: 3.4
%End
QStringList geometryChecks() const;
%Docstring
A list of activated geometry checks.
.. versionadded:: 3.4
%End
void setGeometryChecks( const QStringList &geometryChecks );
%Docstring
A list of activated geometry checks.
.. versionadded:: 3.4
%End
QVariantMap checkConfiguration( const QString &checkId ) const;
%Docstring
Access the configuration for the check ``checkId``.
.. versionadded:: 3.4
%End
void setCheckConfiguration( const QString &checkId, const QVariantMap &checkConfiguration );
%Docstring
Set the configuration for the check ``checkId``.
.. versionadded:: 3.4 .. versionadded:: 3.4
%End %End
@ -86,6 +115,38 @@ Write the geometry options to the ``node``.
%Docstring %Docstring
Read the geometry options from ``node``. Read the geometry options from ``node``.
.. versionadded:: 3.4
%End
signals:
void checkConfigurationChanged();
%Docstring
Access the configuration for the check ``checkId``.
.. versionadded:: 3.4
%End
void geometryChecksChanged();
%Docstring
A list of activated geometry checks.
.. versionadded:: 3.4
%End
void removeDuplicateNodesChanged();
%Docstring
Automatically remove duplicate nodes on all geometries which are edited on this layer.
.. versionadded:: 3.4
%End
void geometryPrecisionChanged();
%Docstring
The precision in which geometries on this layer should be saved.
Geometries which are edited on this layer will be rounded to multiples of this value (snap to grid).
Set to 0.0 to disable.
.. versionadded:: 3.4 .. versionadded:: 3.4
%End %End

View File

@ -54,7 +54,6 @@
%Include auto_generated/qgsfields.sip %Include auto_generated/qgsfields.sip
%Include auto_generated/qgsfileutils.sip %Include auto_generated/qgsfileutils.sip
%Include auto_generated/qgsfontutils.sip %Include auto_generated/qgsfontutils.sip
%Include auto_generated/qgsgeometryoptions.sip
%Include auto_generated/qgsgeometrysimplifier.sip %Include auto_generated/qgsgeometrysimplifier.sip
%Include auto_generated/qgshistogram.sip %Include auto_generated/qgshistogram.sip
%Include auto_generated/qgshstoreutils.sip %Include auto_generated/qgshstoreutils.sip
@ -327,6 +326,7 @@
%Include auto_generated/qgsfieldproxymodel.sip %Include auto_generated/qgsfieldproxymodel.sip
%Include auto_generated/qgsfiledownloader.sip %Include auto_generated/qgsfiledownloader.sip
%Include auto_generated/qgsfeaturefiltermodel.sip %Include auto_generated/qgsfeaturefiltermodel.sip
%Include auto_generated/qgsgeometryoptions.sip
%Include auto_generated/qgsgeometryvalidator.sip %Include auto_generated/qgsgeometryvalidator.sip
%Include auto_generated/qgsgml.sip %Include auto_generated/qgsgml.sip
%Include auto_generated/qgsgmlschema.sip %Include auto_generated/qgsgmlschema.sip

View File

@ -152,7 +152,6 @@ SET(QGIS_ANALYSIS_SRCS
vector/geometry_checker/qgsgeometryduplicatecheck.cpp vector/geometry_checker/qgsgeometryduplicatecheck.cpp
vector/geometry_checker/qgsgeometryduplicatenodescheck.cpp vector/geometry_checker/qgsgeometryduplicatenodescheck.cpp
vector/geometry_checker/qgsgeometryfollowboundariescheck.cpp vector/geometry_checker/qgsgeometryfollowboundariescheck.cpp
vector/geometry_checker/qgsgeometrymissingvertexcheck.cpp
vector/geometry_checker/qgsgeometrygapcheck.cpp vector/geometry_checker/qgsgeometrygapcheck.cpp
vector/geometry_checker/qgsgeometrymissingvertexcheck.cpp vector/geometry_checker/qgsgeometrymissingvertexcheck.cpp
vector/geometry_checker/qgsgeometryholecheck.cpp vector/geometry_checker/qgsgeometryholecheck.cpp
@ -186,6 +185,9 @@ SET(QGIS_ANALYSIS_MOC_HDRS
vector/geometry_checker/qgsgeometrychecker.h vector/geometry_checker/qgsgeometrychecker.h
vector/geometry_checker/qgsgeometrycheck.h vector/geometry_checker/qgsgeometrycheck.h
vector/geometry_checker/qgsvectorlayerfeaturepool.h
vector/geometry_checker/qgsgeometrygapcheck.h
vector/geometry_checker/qgsgeometrymissingvertexcheck.h
) )
INCLUDE_DIRECTORIES(SYSTEM ${SPATIALITE_INCLUDE_DIR}) INCLUDE_DIRECTORIES(SYSTEM ${SPATIALITE_INCLUDE_DIR})
@ -252,7 +254,6 @@ SET(QGIS_ANALYSIS_HDRS
vector/geometry_checker/qgsgeometrycheckerutils.h vector/geometry_checker/qgsgeometrycheckerutils.h
vector/geometry_checker/qgsfeaturepool.h vector/geometry_checker/qgsfeaturepool.h
vector/geometry_checker/qgsvectordataproviderfeaturepool.h vector/geometry_checker/qgsvectordataproviderfeaturepool.h
vector/geometry_checker/qgsvectorlayerfeaturepool.h
interpolation/qgsinterpolator.h interpolation/qgsinterpolator.h
interpolation/qgsgridfilewriter.h interpolation/qgsgridfilewriter.h
@ -292,8 +293,6 @@ SET(QGIS_ANALYSIS_HDRS
vector/geometry_checker/qgsgeometryduplicatecheck.h vector/geometry_checker/qgsgeometryduplicatecheck.h
vector/geometry_checker/qgsgeometryduplicatenodescheck.h vector/geometry_checker/qgsgeometryduplicatenodescheck.h
vector/geometry_checker/qgsgeometryfollowboundariescheck.h vector/geometry_checker/qgsgeometryfollowboundariescheck.h
vector/geometry_checker/qgsgeometrygapcheck.h
vector/geometry_checker/qgsgeometrymissingvertexcheck.h
vector/geometry_checker/qgsgeometryholecheck.h vector/geometry_checker/qgsgeometryholecheck.h
vector/geometry_checker/qgsgeometrylineintersectioncheck.h vector/geometry_checker/qgsgeometrylineintersectioncheck.h
vector/geometry_checker/qgsgeometrylinelayerintersectioncheck.h vector/geometry_checker/qgsgeometrylinelayerintersectioncheck.h

View File

@ -62,6 +62,23 @@ bool QgsFeaturePool::getFeature( QgsFeatureId id, QgsFeature &feature )
return true; return true;
} }
QgsFeatureIds QgsFeaturePool::getFeatures( const QgsFeatureRequest &request )
{
QgsFeatureIds fids;
std::unique_ptr<QgsVectorLayerFeatureSource> source = QgsVectorLayerUtils::getFeatureSource( mLayer );
QgsFeatureIterator it = source->getFeatures( request );
QgsFeature feature;
while ( it.nextFeature( feature ) )
{
insertFeature( feature );
fids << feature.id();
}
return fids;
}
QgsFeatureIds QgsFeaturePool::allFeatureIds() const QgsFeatureIds QgsFeaturePool::allFeatureIds() const
{ {
return mFeatureIds; return mFeatureIds;
@ -123,6 +140,11 @@ void QgsFeaturePool::setFeatureIds( const QgsFeatureIds &ids )
mFeatureIds = ids; mFeatureIds = ids;
} }
bool QgsFeaturePool::isFeatureCached( QgsFeatureId fid )
{
return mFeatureCache.contains( fid );
}
QgsCoordinateReferenceSystem QgsFeaturePool::crs() const QgsCoordinateReferenceSystem QgsFeaturePool::crs() const
{ {
return mCrs; return mCrs;

View File

@ -31,6 +31,9 @@ class QgsVectorLayer;
/** /**
* \ingroup analysis * \ingroup analysis
* A feature pool is based on a vector layer and caches features. * A feature pool is based on a vector layer and caches features.
*
* \note This class is a technology preview and unstable API.
* \since QGIS 3.4
*/ */
class ANALYSIS_EXPORT QgsFeaturePool : public QgsFeatureSink SIP_ABSTRACT class ANALYSIS_EXPORT QgsFeaturePool : public QgsFeatureSink SIP_ABSTRACT
{ {
@ -46,6 +49,15 @@ class ANALYSIS_EXPORT QgsFeaturePool : public QgsFeatureSink SIP_ABSTRACT
*/ */
bool getFeature( QgsFeatureId id, QgsFeature &feature ); bool getFeature( QgsFeatureId id, QgsFeature &feature );
/**
* Get features for the provided \a request. No features will be fetched
* from the cache and the request is sent directly to the underlying feature source.
* Results of the request are cached in the pool and the ids of all the features
* are returned. This can be used to warm the cache for a particular area of interest
* (bounding box) or other set of features.
*/
QgsFeatureIds getFeatures( const QgsFeatureRequest &request ) SIP_SKIP;
/** /**
* Updates a feature in this pool. * Updates a feature in this pool.
* Implementations will update the feature on the layer or on the data provider. * Implementations will update the feature on the layer or on the data provider.
@ -136,6 +148,14 @@ class ANALYSIS_EXPORT QgsFeaturePool : public QgsFeatureSink SIP_ABSTRACT
*/ */
void setFeatureIds( const QgsFeatureIds &ids ) SIP_SKIP; void setFeatureIds( const QgsFeatureIds &ids ) SIP_SKIP;
/**
* Checks if the feature \a fid is cached.
*
* \note not available in Python bindings
* \since QGIS 3.4
*/
bool isFeatureCached( QgsFeatureId fid ) SIP_SKIP;
private: private:
#ifdef SIP_RUN #ifdef SIP_RUN
QgsFeaturePool( const QgsFeaturePool &other ) QgsFeaturePool( const QgsFeaturePool &other )

View File

@ -34,7 +34,7 @@ class ANALYSIS_EXPORT QgsGeometryAngleCheck : public QgsGeometryCheck
, mMinAngle( configuration.value( QStringLiteral( "minAngle" ), 0.0 ).toDouble() ) , mMinAngle( configuration.value( QStringLiteral( "minAngle" ), 0.0 ).toDouble() )
{} {}
void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback = nullptr, const LayerFeatureIds &ids = LayerFeatureIds() ) const override; void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids = LayerFeatureIds() ) const override;
void fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override; void fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override;
QList<QgsWkbTypes::GeometryType> compatibleGeometryTypes() const override; QList<QgsWkbTypes::GeometryType> compatibleGeometryTypes() const override;

View File

@ -32,7 +32,7 @@ class ANALYSIS_EXPORT QgsGeometryAreaCheck : public QgsGeometryCheck
, mAreaThreshold( configurationValue<double>( "areaThreshold" ) ) , mAreaThreshold( configurationValue<double>( "areaThreshold" ) )
{} {}
QList<QgsWkbTypes::GeometryType> compatibleGeometryTypes() const override { return factoryCompatibleGeometryTypes(); } QList<QgsWkbTypes::GeometryType> compatibleGeometryTypes() const override { return factoryCompatibleGeometryTypes(); }
void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback = nullptr, const LayerFeatureIds &ids = LayerFeatureIds() ) const override; void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids = LayerFeatureIds() ) const override;
void fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override; void fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override;
QStringList resolutionMethods() const override; QStringList resolutionMethods() const override;
QString id() const override { return factoryId(); } QString id() const override { return factoryId(); }

View File

@ -31,8 +31,16 @@
class QgsGeometryCheckError; class QgsGeometryCheckError;
class QgsFeaturePool; class QgsFeaturePool;
#define FEATUREID_NULL std::numeric_limits<QgsFeatureId>::min() /**
* \ingroup analysis
* This class manages all known geometry check factories.
*
* QgsGeometryCheckRegistry is not usually directly created, but rather accessed through
* QgsAnalysis::geometryCheckRegistry().
*
* \note This class is a technology preview and unstable API.
* \since QGIS 3.4
*/
class ANALYSIS_EXPORT QgsGeometryCheck class ANALYSIS_EXPORT QgsGeometryCheck
{ {
Q_GADGET Q_GADGET
@ -200,7 +208,7 @@ class ANALYSIS_EXPORT QgsGeometryCheck
* *
* \since QGIS 3.4 * \since QGIS 3.4
*/ */
virtual void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors SIP_INOUT, QStringList &messages SIP_INOUT, QgsFeedback *feedback = nullptr, const LayerFeatureIds &ids = QgsGeometryCheck::LayerFeatureIds() ) const = 0; virtual void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors SIP_INOUT, QStringList &messages SIP_INOUT, QgsFeedback *feedback, const LayerFeatureIds &ids = QgsGeometryCheck::LayerFeatureIds() ) const = 0;
/** /**
* Fix the error \a error with the specified \a method. * Fix the error \a error with the specified \a method.

View File

@ -24,6 +24,7 @@
/** /**
* Base configuration for geometry checks. * Base configuration for geometry checks.
* *
* \note This class is a technology preview and unstable API.
* \since QGIS 3.4 * \since QGIS 3.4
*/ */
struct ANALYSIS_EXPORT QgsGeometryCheckContext struct ANALYSIS_EXPORT QgsGeometryCheckContext

View File

@ -25,7 +25,13 @@
class QgsPointXY; class QgsPointXY;
/**
* \ingroup analysis
* This represents an error reported by a geometry check.
*
* \note This class is a technology preview and unstable API.
* \since QGIS 3.4
*/
class ANALYSIS_EXPORT QgsGeometryCheckError class ANALYSIS_EXPORT QgsGeometryCheckError
{ {
public: public:

View File

@ -302,12 +302,12 @@ QList<const QgsLineString *> QgsGeometryCheckerUtils::polygonRings( const QgsPol
void QgsGeometryCheckerUtils::filter1DTypes( QgsAbstractGeometry *geom ) void QgsGeometryCheckerUtils::filter1DTypes( QgsAbstractGeometry *geom )
{ {
if ( dynamic_cast<QgsGeometryCollection *>( geom ) ) if ( qgsgeometry_cast<QgsGeometryCollection *>( geom ) )
{ {
QgsGeometryCollection *geomCollection = static_cast<QgsGeometryCollection *>( geom ); QgsGeometryCollection *geomCollection = static_cast<QgsGeometryCollection *>( geom );
for ( int nParts = geom->partCount(), iPart = nParts - 1; iPart >= 0; --iPart ) for ( int nParts = geom->partCount(), iPart = nParts - 1; iPart >= 0; --iPart )
{ {
if ( !dynamic_cast<QgsSurface *>( geomCollection->geometryN( iPart ) ) ) if ( !qgsgeometry_cast<QgsSurface *>( geomCollection->geometryN( iPart ) ) )
{ {
geomCollection->removeGeometry( iPart ); geomCollection->removeGeometry( iPart );
} }

View File

@ -28,6 +28,15 @@ class QgsGeometryEngine;
class QgsFeaturePool; class QgsFeaturePool;
class QgsFeedback; class QgsFeedback;
/**
* \ingroup analysis
*
* Contains utilities required for geometry checks.
*
* \note This class is a technology preview and unstable API.
* \since QGIS 3.4
*/
class ANALYSIS_EXPORT QgsGeometryCheckerUtils class ANALYSIS_EXPORT QgsGeometryCheckerUtils
{ {
public: public:

View File

@ -34,6 +34,11 @@ struct QgsGeometryCheckContext;
/** /**
* \ingroup analysis * \ingroup analysis
*
* A factory for geometry checks.
*
* \note This class is a technology preview and unstable API.
* \since QGIS 3.4
*/ */
class ANALYSIS_EXPORT QgsGeometryCheckFactory SIP_ABSTRACT class ANALYSIS_EXPORT QgsGeometryCheckFactory SIP_ABSTRACT
{ {

View File

@ -35,6 +35,7 @@ struct QgsGeometryCheckContext;
* QgsGeometryCheckRegistry is not usually directly created, but rather accessed through * QgsGeometryCheckRegistry is not usually directly created, but rather accessed through
* QgsAnalysis::geometryCheckRegistry(). * QgsAnalysis::geometryCheckRegistry().
* *
* \note This class is a technology preview and unstable API.
* \since QGIS 3.4 * \since QGIS 3.4
*/ */
class ANALYSIS_EXPORT QgsGeometryCheckRegistry class ANALYSIS_EXPORT QgsGeometryCheckRegistry

View File

@ -57,7 +57,7 @@ class ANALYSIS_EXPORT QgsGeometryContainedCheck : public QgsGeometryCheck
explicit QgsGeometryContainedCheck( QgsGeometryCheckContext *context, const QVariantMap &configuration ) explicit QgsGeometryContainedCheck( QgsGeometryCheckContext *context, const QVariantMap &configuration )
: QgsGeometryCheck( context, configuration ) {} : QgsGeometryCheck( context, configuration ) {}
QList<QgsWkbTypes::GeometryType> compatibleGeometryTypes() const override { return factoryCompatibleGeometryTypes(); } QList<QgsWkbTypes::GeometryType> compatibleGeometryTypes() const override { return factoryCompatibleGeometryTypes(); }
void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback = nullptr, const LayerFeatureIds &ids = LayerFeatureIds() ) const override; void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids = LayerFeatureIds() ) const override;
void fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override; void fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override;
QStringList resolutionMethods() const override; QStringList resolutionMethods() const override;
QString id() const override { return factoryId(); } QString id() const override { return factoryId(); }

View File

@ -29,7 +29,7 @@ class ANALYSIS_EXPORT QgsGeometryDangleCheck : public QgsGeometryCheck
void fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override; void fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override;
QList<QgsWkbTypes::GeometryType> compatibleGeometryTypes() const override { return factoryCompatibleGeometryTypes(); } QList<QgsWkbTypes::GeometryType> compatibleGeometryTypes() const override { return factoryCompatibleGeometryTypes(); }
void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback = nullptr, const LayerFeatureIds &ids = LayerFeatureIds() ) const override; void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids = LayerFeatureIds() ) const override;
QStringList resolutionMethods() const override; QStringList resolutionMethods() const override;
QString description() const override { return factoryDescription(); } QString description() const override { return factoryDescription(); }
QString id() const override { return factoryId(); } QString id() const override { return factoryId(); }

View File

@ -28,7 +28,7 @@ class ANALYSIS_EXPORT QgsGeometryDegeneratePolygonCheck : public QgsGeometryChec
explicit QgsGeometryDegeneratePolygonCheck( QgsGeometryCheckContext *context, const QVariantMap &configuration ) explicit QgsGeometryDegeneratePolygonCheck( QgsGeometryCheckContext *context, const QVariantMap &configuration )
: QgsGeometryCheck( context, configuration ) {} : QgsGeometryCheck( context, configuration ) {}
void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback = nullptr, const LayerFeatureIds &ids = LayerFeatureIds() ) const override; void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids = LayerFeatureIds() ) const override;
void fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override; void fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override;
QList<QgsWkbTypes::GeometryType> compatibleGeometryTypes() const override { return factoryCompatibleGeometryTypes(); } QList<QgsWkbTypes::GeometryType> compatibleGeometryTypes() const override { return factoryCompatibleGeometryTypes(); }

View File

@ -55,7 +55,7 @@ class ANALYSIS_EXPORT QgsGeometryDuplicateCheck : public QgsGeometryCheck
public: public:
explicit QgsGeometryDuplicateCheck( QgsGeometryCheckContext *context, const QVariantMap &configuration ) explicit QgsGeometryDuplicateCheck( QgsGeometryCheckContext *context, const QVariantMap &configuration )
: QgsGeometryCheck( context, configuration ) {} : QgsGeometryCheck( context, configuration ) {}
void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback = nullptr, const LayerFeatureIds &ids = LayerFeatureIds() ) const override; void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids = LayerFeatureIds() ) const override;
void fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override; void fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override;
QList<QgsWkbTypes::GeometryType> compatibleGeometryTypes() const override { return factoryCompatibleGeometryTypes(); } QList<QgsWkbTypes::GeometryType> compatibleGeometryTypes() const override { return factoryCompatibleGeometryTypes(); }

View File

@ -28,7 +28,7 @@ class ANALYSIS_EXPORT QgsGeometryDuplicateNodesCheck : public QgsGeometryCheck
static QList<QgsWkbTypes::GeometryType> factoryCompatibleGeometryTypes() {return {QgsWkbTypes::LineGeometry, QgsWkbTypes::PolygonGeometry}; } static QList<QgsWkbTypes::GeometryType> factoryCompatibleGeometryTypes() {return {QgsWkbTypes::LineGeometry, QgsWkbTypes::PolygonGeometry}; }
static bool factoryIsCompatible( QgsVectorLayer *layer ) SIP_SKIP { return factoryCompatibleGeometryTypes().contains( layer->geometryType() ); } static bool factoryIsCompatible( QgsVectorLayer *layer ) SIP_SKIP { return factoryCompatibleGeometryTypes().contains( layer->geometryType() ); }
QList<QgsWkbTypes::GeometryType> compatibleGeometryTypes() const override { return factoryCompatibleGeometryTypes(); } QList<QgsWkbTypes::GeometryType> compatibleGeometryTypes() const override { return factoryCompatibleGeometryTypes(); }
void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback = nullptr, const LayerFeatureIds &ids = LayerFeatureIds() ) const override; void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids = LayerFeatureIds() ) const override;
void fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override; void fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override;
QStringList resolutionMethods() const override; QStringList resolutionMethods() const override;
static QString factoryDescription() { return tr( "Duplicate node" ); } static QString factoryDescription() { return tr( "Duplicate node" ); }

View File

@ -31,7 +31,7 @@ class ANALYSIS_EXPORT QgsGeometryFollowBoundariesCheck : public QgsGeometryCheck
static QList<QgsWkbTypes::GeometryType> factoryCompatibleGeometryTypes() {return {QgsWkbTypes::PolygonGeometry}; } static QList<QgsWkbTypes::GeometryType> factoryCompatibleGeometryTypes() {return {QgsWkbTypes::PolygonGeometry}; }
static bool factoryIsCompatible( QgsVectorLayer *layer ) SIP_SKIP { return factoryCompatibleGeometryTypes().contains( layer->geometryType() ); } static bool factoryIsCompatible( QgsVectorLayer *layer ) SIP_SKIP { return factoryCompatibleGeometryTypes().contains( layer->geometryType() ); }
QList<QgsWkbTypes::GeometryType> compatibleGeometryTypes() const override { return factoryCompatibleGeometryTypes(); } QList<QgsWkbTypes::GeometryType> compatibleGeometryTypes() const override { return factoryCompatibleGeometryTypes(); }
void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback = nullptr, const LayerFeatureIds &ids = LayerFeatureIds() ) const override; void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids = LayerFeatureIds() ) const override;
void fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override; void fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override;
QStringList resolutionMethods() const override; QStringList resolutionMethods() const override;
static QString factoryDescription() { return tr( "Polygon does not follow boundaries" ); } static QString factoryDescription() { return tr( "Polygon does not follow boundaries" ); }

View File

@ -44,6 +44,13 @@ void QgsGeometryGapCheck::collectErrors( const QMap<QString, QgsFeaturePool *> &
for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeature : layerFeatures ) for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeature : layerFeatures )
{ {
geomList.append( layerFeature.geometry().constGet()->clone() ); geomList.append( layerFeature.geometry().constGet()->clone() );
if ( feedback->isCanceled() )
{
qDeleteAll( geomList );
geomList.clear();
break;
}
} }
if ( geomList.isEmpty() ) if ( geomList.isEmpty() )
@ -97,7 +104,7 @@ void QgsGeometryGapCheck::collectErrors( const QMap<QString, QgsFeaturePool *> &
} }
// Skip gaps above threshold // Skip gaps above threshold
if ( gapGeom->area() > mGapThresholdMapUnits || gapGeom->area() < mContext->reducedTolerance ) if ( ( mGapThresholdMapUnits > 0 && gapGeom->area() > mGapThresholdMapUnits ) || gapGeom->area() < mContext->reducedTolerance )
{ {
continue; continue;
} }
@ -124,31 +131,36 @@ void QgsGeometryGapCheck::collectErrors( const QMap<QString, QgsFeaturePool *> &
// Add error // Add error
double area = gapGeom->area(); double area = gapGeom->area();
errors.append( new QgsGeometryGapCheckError( this, QString(), QgsGeometry( gapGeom.release() ), neighboringIds, area, gapAreaBBox ) ); errors.append( new QgsGeometryGapCheckError( this, QString(), QgsGeometry( gapGeom.release() ), neighboringIds, area, gapAreaBBox ) );
} }
} }
void QgsGeometryGapCheck::fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> & /*mergeAttributeIndices*/, Changes &changes ) const void QgsGeometryGapCheck::fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> & /*mergeAttributeIndices*/, Changes &changes ) const
{ {
if ( method == NoChange ) QMetaEnum metaEnum = QMetaEnum::fromType<QgsGeometryGapCheck::ResolutionMethod>();
if ( !metaEnum.isValid() || !metaEnum.valueToKey( method ) )
{ {
error->setFixed( method ); error->setFixFailed( tr( "Unknown method" ) );
}
else if ( method == MergeLongestEdge )
{
QString errMsg;
if ( mergeWithNeighbor( featurePools, static_cast<QgsGeometryGapCheckError *>( error ), changes, errMsg ) )
{
error->setFixed( method );
}
else
{
error->setFixFailed( tr( "Failed to merge with neighbor: %1" ).arg( errMsg ) );
}
} }
else else
{ {
error->setFixFailed( tr( "Unknown method" ) ); ResolutionMethod methodValue = static_cast<ResolutionMethod>( method );
switch ( methodValue )
{
case NoChange:
error->setFixed( method );
break;
case MergeLongestEdge:
QString errMsg;
if ( mergeWithNeighbor( featurePools, static_cast<QgsGeometryGapCheckError *>( error ), changes, errMsg ) )
{
error->setFixed( method );
}
else
{
error->setFixFailed( tr( "Failed to merge with neighbor: %1" ).arg( errMsg ) );
}
break;
}
} }
} }

View File

@ -20,6 +20,7 @@
#include "qgsgeometrycheck.h" #include "qgsgeometrycheck.h"
#include "qgsgeometrycheckerror.h" #include "qgsgeometrycheckerror.h"
#include "qgsfeatureid.h"
class ANALYSIS_EXPORT QgsGeometryGapCheckError : public QgsGeometryCheckError class ANALYSIS_EXPORT QgsGeometryGapCheckError : public QgsGeometryCheckError
{ {
@ -30,7 +31,7 @@ class ANALYSIS_EXPORT QgsGeometryGapCheckError : public QgsGeometryCheckError
const QMap<QString, QgsFeatureIds> &neighbors, const QMap<QString, QgsFeatureIds> &neighbors,
double area, double area,
const QgsRectangle &gapAreaBBox ) const QgsRectangle &gapAreaBBox )
: QgsGeometryCheckError( check, layerId, FEATUREID_NULL, geometry, geometry.constGet()->centroid(), QgsVertexId(), area, ValueArea ) : QgsGeometryCheckError( check, layerId, FID_NULL, geometry, geometry.constGet()->centroid(), QgsVertexId(), area, ValueArea )
, mNeighbors( neighbors ) , mNeighbors( neighbors )
, mGapAreaBBox( gapAreaBBox ) , mGapAreaBBox( gapAreaBBox )
{ {
@ -75,11 +76,26 @@ class ANALYSIS_EXPORT QgsGeometryGapCheckError : public QgsGeometryCheckError
class ANALYSIS_EXPORT QgsGeometryGapCheck : public QgsGeometryCheck class ANALYSIS_EXPORT QgsGeometryGapCheck : public QgsGeometryCheck
{ {
Q_GADGET
public: public:
//! Resolution methods for geometry gap checks
enum ResolutionMethod
{
MergeLongestEdge,
NoChange
};
Q_ENUM( ResolutionMethod )
/**
* The \a configuration accepts a "gapThreshold" key which specifies
* the maximum gap size in squared map units. Any gaps which are larger
* than this area are accepted. If "gapThreshold" is set to 0, the check
* is disabled.
*/
explicit QgsGeometryGapCheck( const QgsGeometryCheckContext *context, const QVariantMap &configuration ); explicit QgsGeometryGapCheck( const QgsGeometryCheckContext *context, const QVariantMap &configuration );
QList<QgsWkbTypes::GeometryType> compatibleGeometryTypes() const override { return factoryCompatibleGeometryTypes(); } QList<QgsWkbTypes::GeometryType> compatibleGeometryTypes() const override { return factoryCompatibleGeometryTypes(); }
void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback = nullptr, const LayerFeatureIds &ids = LayerFeatureIds() ) const override; void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids = LayerFeatureIds() ) const override;
void fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override; void fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override;
QStringList resolutionMethods() const override; QStringList resolutionMethods() const override;
@ -97,8 +113,6 @@ class ANALYSIS_EXPORT QgsGeometryGapCheck : public QgsGeometryCheck
static QgsGeometryCheck::CheckType factoryCheckType() SIP_SKIP; static QgsGeometryCheck::CheckType factoryCheckType() SIP_SKIP;
///@endcond private ///@endcond private
enum ResolutionMethod { MergeLongestEdge, NoChange };
private: private:
bool mergeWithNeighbor( const QMap<QString, QgsFeaturePool *> &featurePools, bool mergeWithNeighbor( const QMap<QString, QgsFeaturePool *> &featurePools,
QgsGeometryGapCheckError *err, Changes &changes, QString &errMsg ) const; QgsGeometryGapCheckError *err, Changes &changes, QString &errMsg ) const;

View File

@ -28,7 +28,7 @@ class ANALYSIS_EXPORT QgsGeometryHoleCheck : public QgsGeometryCheck
static QList<QgsWkbTypes::GeometryType> factoryCompatibleGeometryTypes() {return {QgsWkbTypes::PolygonGeometry}; } static QList<QgsWkbTypes::GeometryType> factoryCompatibleGeometryTypes() {return {QgsWkbTypes::PolygonGeometry}; }
static bool factoryIsCompatible( QgsVectorLayer *layer ) SIP_SKIP { return factoryCompatibleGeometryTypes().contains( layer->geometryType() ); } static bool factoryIsCompatible( QgsVectorLayer *layer ) SIP_SKIP { return factoryCompatibleGeometryTypes().contains( layer->geometryType() ); }
QList<QgsWkbTypes::GeometryType> compatibleGeometryTypes() const override { return factoryCompatibleGeometryTypes(); } QList<QgsWkbTypes::GeometryType> compatibleGeometryTypes() const override { return factoryCompatibleGeometryTypes(); }
void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback = nullptr, const LayerFeatureIds &ids = LayerFeatureIds() ) const override; void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids = LayerFeatureIds() ) const override;
void fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override; void fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override;
QStringList resolutionMethods() const override; QStringList resolutionMethods() const override;
static QString factoryDescription() { return tr( "Polygon with hole" ); } static QString factoryDescription() { return tr( "Polygon with hole" ); }

View File

@ -49,7 +49,11 @@ QList<QgsSingleGeometryCheckError *> QgsGeometryIsValidCheck::processGeometry( c
QList<QgsSingleGeometryCheckError *> result; QList<QgsSingleGeometryCheckError *> result;
for ( const auto &error : qgis::as_const( errors ) ) for ( const auto &error : qgis::as_const( errors ) )
{ {
result << new QgsSingleGeometryCheckError( this, geometry, QgsGeometry( qgis::make_unique<QgsPoint>( error.where() ) ) ); QgsGeometry errorGeometry;
if ( error.hasWhere() )
errorGeometry = QgsGeometry( qgis::make_unique<QgsPoint>( error.where() ) );
result << new QgsGeometryIsValidCheckError( this, geometry, errorGeometry, error.what() );
} }
return result; return result;
} }
@ -89,3 +93,15 @@ QgsGeometryCheck::CheckType QgsGeometryIsValidCheck::factoryCheckType()
return QgsGeometryCheck::FeatureNodeCheck; return QgsGeometryCheck::FeatureNodeCheck;
} }
///@endcond ///@endcond
QgsGeometryIsValidCheckError::QgsGeometryIsValidCheckError( const QgsSingleGeometryCheck *check, const QgsGeometry &geometry, const QgsGeometry &errorLocation, const QString &errorDescription )
: QgsSingleGeometryCheckError( check, geometry, errorLocation )
, mDescription( errorDescription )
{
}
QString QgsGeometryIsValidCheckError::description() const
{
return mDescription;
}

View File

@ -20,6 +20,17 @@ email : matthias@opengis.ch
#include "qgssinglegeometrycheck.h" #include "qgssinglegeometrycheck.h"
class ANALYSIS_EXPORT QgsGeometryIsValidCheckError : public QgsSingleGeometryCheckError
{
public:
QgsGeometryIsValidCheckError( const QgsSingleGeometryCheck *check, const QgsGeometry &geometry, const QgsGeometry &errorLocation, const QString &errorDescription );
QString description() const override;
private:
QString mDescription;
};
/** /**
* Checks if geometries are valid using the backend configured in the QGIS settings. * Checks if geometries are valid using the backend configured in the QGIS settings.
* This does not offer any fixes but makes sure that all geometries are valid. * This does not offer any fixes but makes sure that all geometries are valid.

View File

@ -29,7 +29,7 @@ class ANALYSIS_EXPORT QgsGeometryLineIntersectionCheck : public QgsGeometryCheck
static QList<QgsWkbTypes::GeometryType> factoryCompatibleGeometryTypes() {return {QgsWkbTypes::LineGeometry}; } static QList<QgsWkbTypes::GeometryType> factoryCompatibleGeometryTypes() {return {QgsWkbTypes::LineGeometry}; }
static bool factoryIsCompatible( QgsVectorLayer *layer ) SIP_SKIP { return factoryCompatibleGeometryTypes().contains( layer->geometryType() ); } static bool factoryIsCompatible( QgsVectorLayer *layer ) SIP_SKIP { return factoryCompatibleGeometryTypes().contains( layer->geometryType() ); }
QList<QgsWkbTypes::GeometryType> compatibleGeometryTypes() const override { return factoryCompatibleGeometryTypes(); } QList<QgsWkbTypes::GeometryType> compatibleGeometryTypes() const override { return factoryCompatibleGeometryTypes(); }
void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback = nullptr, const LayerFeatureIds &ids = LayerFeatureIds() ) const override; void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids = LayerFeatureIds() ) const override;
void fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override; void fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override;
QStringList resolutionMethods() const override; QStringList resolutionMethods() const override;
static QString factoryDescription() { return tr( "Intersection" ); } static QString factoryDescription() { return tr( "Intersection" ); }

View File

@ -30,7 +30,7 @@ class ANALYSIS_EXPORT QgsGeometryLineLayerIntersectionCheck : public QgsGeometry
static QList<QgsWkbTypes::GeometryType> factoryCompatibleGeometryTypes() {return {QgsWkbTypes::LineGeometry}; } static QList<QgsWkbTypes::GeometryType> factoryCompatibleGeometryTypes() {return {QgsWkbTypes::LineGeometry}; }
static bool factoryIsCompatible( QgsVectorLayer *layer ) SIP_SKIP { return factoryCompatibleGeometryTypes().contains( layer->geometryType() ); } static bool factoryIsCompatible( QgsVectorLayer *layer ) SIP_SKIP { return factoryCompatibleGeometryTypes().contains( layer->geometryType() ); }
QList<QgsWkbTypes::GeometryType> compatibleGeometryTypes() const override { return factoryCompatibleGeometryTypes(); } QList<QgsWkbTypes::GeometryType> compatibleGeometryTypes() const override { return factoryCompatibleGeometryTypes(); }
void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback = nullptr, const LayerFeatureIds &ids = LayerFeatureIds() ) const override; void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids = LayerFeatureIds() ) const override;
void fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override; void fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override;
QStringList resolutionMethods() const override; QStringList resolutionMethods() const override;
static QString factoryDescription() { return tr( "Intersection" ); } static QString factoryDescription() { return tr( "Intersection" ); }

View File

@ -27,9 +27,7 @@
QgsGeometryMissingVertexCheck::QgsGeometryMissingVertexCheck( const QgsGeometryCheckContext *context, const QVariantMap &geometryCheckConfiguration ) QgsGeometryMissingVertexCheck::QgsGeometryMissingVertexCheck( const QgsGeometryCheckContext *context, const QVariantMap &geometryCheckConfiguration )
: QgsGeometryCheck( context, geometryCheckConfiguration ) : QgsGeometryCheck( context, geometryCheckConfiguration )
{ {}
}
void QgsGeometryMissingVertexCheck::collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids ) const void QgsGeometryMissingVertexCheck::collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids ) const
{ {
@ -45,11 +43,16 @@ void QgsGeometryMissingVertexCheck::collectErrors( const QMap<QString, QgsFeatur
for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeature : layerFeatures ) for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeature : layerFeatures )
{ {
if ( feedback->isCanceled() )
{
break;
}
const QgsAbstractGeometry *geom = layerFeature.geometry().constGet(); const QgsAbstractGeometry *geom = layerFeature.geometry().constGet();
if ( QgsCurvePolygon *polygon = qgsgeometry_cast<QgsCurvePolygon *>( geom ) ) if ( QgsCurvePolygon *polygon = qgsgeometry_cast<QgsCurvePolygon *>( geom ) )
{ {
processPolygon( polygon, featurePool, errors, layerFeature ); processPolygon( polygon, featurePool, errors, layerFeature, feedback );
} }
else if ( QgsGeometryCollection *collection = qgsgeometry_cast<QgsGeometryCollection *>( geom ) ) else if ( QgsGeometryCollection *collection = qgsgeometry_cast<QgsGeometryCollection *>( geom ) )
{ {
@ -58,7 +61,7 @@ void QgsGeometryMissingVertexCheck::collectErrors( const QMap<QString, QgsFeatur
{ {
if ( QgsCurvePolygon *polygon = qgsgeometry_cast<QgsCurvePolygon *>( collection->geometryN( i ) ) ) if ( QgsCurvePolygon *polygon = qgsgeometry_cast<QgsCurvePolygon *>( collection->geometryN( i ) ) )
{ {
processPolygon( polygon, featurePool, errors, layerFeature ); processPolygon( polygon, featurePool, errors, layerFeature, feedback );
} }
} }
} }
@ -69,15 +72,50 @@ void QgsGeometryMissingVertexCheck::fixError( const QMap<QString, QgsFeaturePool
{ {
Q_UNUSED( featurePools ) Q_UNUSED( featurePools )
Q_UNUSED( changes ) Q_UNUSED( changes )
if ( method == NoChange )
QMetaEnum metaEnum = QMetaEnum::fromType<QgsGeometryMissingVertexCheck::ResolutionMethod>();
if ( !metaEnum.isValid() || !metaEnum.valueToKey( method ) )
{ {
error->setFixed( method ); error->setFixFailed( tr( "Unknown method" ) );
}
else
{
ResolutionMethod methodValue = static_cast<ResolutionMethod>( method );
switch ( methodValue )
{
case NoChange:
error->setFixed( method );
break;
case AddMissingVertex:
{
QgsFeaturePool *featurePool = featurePools[ error->layerId() ];
QgsFeature feature;
featurePool->getFeature( error->featureId(), feature );
QgsPointXY pointOnSegment; // Should be equal to location
int vertexIndex;
QgsGeometry geometry = feature.geometry();
geometry.closestSegmentWithContext( error->location(), pointOnSegment, vertexIndex );
geometry.insertVertex( QgsPoint( error->location() ), vertexIndex );
feature.setGeometry( geometry );
featurePool->updateFeature( feature );
// TODO update "changes" structure
error->setFixed( method );
}
break;
}
} }
} }
QStringList QgsGeometryMissingVertexCheck::resolutionMethods() const QStringList QgsGeometryMissingVertexCheck::resolutionMethods() const
{ {
static QStringList methods = QStringList() << tr( "No action" ); static QStringList methods = QStringList()
<< tr( "No action" )
<< tr( "Add missing vertex" );
return methods; return methods;
} }
@ -86,7 +124,7 @@ QString QgsGeometryMissingVertexCheck::description() const
return factoryDescription(); return factoryDescription();
} }
void QgsGeometryMissingVertexCheck::processPolygon( const QgsCurvePolygon *polygon, QgsFeaturePool *featurePool, QList<QgsGeometryCheckError *> &errors, const QgsGeometryCheckerUtils::LayerFeature &layerFeature ) const void QgsGeometryMissingVertexCheck::processPolygon( const QgsCurvePolygon *polygon, QgsFeaturePool *featurePool, QList<QgsGeometryCheckError *> &errors, const QgsGeometryCheckerUtils::LayerFeature &layerFeature, QgsFeedback *feedback ) const
{ {
const QgsFeature &currentFeature = layerFeature.feature(); const QgsFeature &currentFeature = layerFeature.feature();
std::unique_ptr<QgsMultiPolygon> boundaries = qgis::make_unique<QgsMultiPolygon>(); std::unique_ptr<QgsMultiPolygon> boundaries = qgis::make_unique<QgsMultiPolygon>();
@ -114,6 +152,9 @@ void QgsGeometryMissingVertexCheck::processPolygon( const QgsCurvePolygon *polyg
if ( featurePool->getFeature( fid, compareFeature ) ) if ( featurePool->getFeature( fid, compareFeature ) )
{ {
if ( feedback->isCanceled() )
break;
QgsVertexIterator vertexIterator = compareFeature.geometry().vertices(); QgsVertexIterator vertexIterator = compareFeature.geometry().vertices();
while ( vertexIterator.hasNext() ) while ( vertexIterator.hasNext() )
{ {

View File

@ -34,14 +34,18 @@ class QgsCurvePolygon;
*/ */
class ANALYSIS_EXPORT QgsGeometryMissingVertexCheck : public QgsGeometryCheck class ANALYSIS_EXPORT QgsGeometryMissingVertexCheck : public QgsGeometryCheck
{ {
Q_GADGET
public: public:
enum ResolutionMethod enum ResolutionMethod
{ {
NoChange NoChange,
AddMissingVertex
}; };
Q_ENUM( ResolutionMethod )
explicit QgsGeometryMissingVertexCheck( const QgsGeometryCheckContext *context, const QVariantMap &geometryCheckConfiguration ); explicit QgsGeometryMissingVertexCheck( const QgsGeometryCheckContext *context, const QVariantMap &geometryCheckConfiguration );
void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback = nullptr, const LayerFeatureIds &ids = LayerFeatureIds() ) const override; void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids = LayerFeatureIds() ) const override;
void fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override; void fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override;
QStringList resolutionMethods() const override; QStringList resolutionMethods() const override;
@ -61,7 +65,7 @@ class ANALYSIS_EXPORT QgsGeometryMissingVertexCheck : public QgsGeometryCheck
///@endcond ///@endcond
private: private:
void processPolygon( const QgsCurvePolygon *polygon, QgsFeaturePool *featurePool, QList<QgsGeometryCheckError *> &errors, const QgsGeometryCheckerUtils::LayerFeature &layerFeature ) const; void processPolygon( const QgsCurvePolygon *polygon, QgsFeaturePool *featurePool, QList<QgsGeometryCheckError *> &errors, const QgsGeometryCheckerUtils::LayerFeature &layerFeature, QgsFeedback *feedback ) const;
}; };

View File

@ -18,7 +18,7 @@
#include "qgsgeometryoverlapcheck.h" #include "qgsgeometryoverlapcheck.h"
#include "qgsfeaturepool.h" #include "qgsfeaturepool.h"
#include "qgsvectorlayer.h" #include "qgsvectorlayer.h"
#include "qgsfeedback.h"
QgsGeometryOverlapCheck::QgsGeometryOverlapCheck( const QgsGeometryCheckContext *context, const QVariantMap &configuration ) QgsGeometryOverlapCheck::QgsGeometryOverlapCheck( const QgsGeometryCheckContext *context, const QVariantMap &configuration )
: QgsGeometryCheck( context, configuration ) : QgsGeometryCheck( context, configuration )
@ -35,6 +35,9 @@ void QgsGeometryOverlapCheck::collectErrors( const QMap<QString, QgsFeaturePool
QList<QString> layerIds = featureIds.keys(); QList<QString> layerIds = featureIds.keys();
for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeatureA : layerFeaturesA ) for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeatureA : layerFeaturesA )
{ {
if ( feedback->isCanceled() )
break;
// Ensure each pair of layers only gets compared once: remove the current layer from the layerIds, but add it to the layerList for layerFeaturesB // Ensure each pair of layers only gets compared once: remove the current layer from the layerIds, but add it to the layerList for layerFeaturesB
layerIds.removeOne( layerFeatureA.layer()->id() ); layerIds.removeOne( layerFeatureA.layer()->id() );
@ -49,6 +52,9 @@ void QgsGeometryOverlapCheck::collectErrors( const QMap<QString, QgsFeaturePool
const QgsGeometryCheckerUtils::LayerFeatures layerFeaturesB( featurePools, QList<QString>() << layerFeatureA.layer()->id() << layerIds, bboxA, compatibleGeometryTypes(), mContext ); const QgsGeometryCheckerUtils::LayerFeatures layerFeaturesB( featurePools, QList<QString>() << layerFeatureA.layer()->id() << layerIds, bboxA, compatibleGeometryTypes(), mContext );
for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeatureB : layerFeaturesB ) for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeatureB : layerFeaturesB )
{ {
if ( feedback->isCanceled() )
break;
// > : only report overlaps within same layer once // > : only report overlaps within same layer once
if ( layerFeatureA.layer()->id() == layerFeatureB.layer()->id() && layerFeatureB.feature().id() >= layerFeatureA.feature().id() ) if ( layerFeatureA.layer()->id() == layerFeatureB.layer()->id() && layerFeatureB.feature().id() >= layerFeatureA.feature().id() )
{ {
@ -57,15 +63,15 @@ void QgsGeometryOverlapCheck::collectErrors( const QMap<QString, QgsFeaturePool
QString errMsg; QString errMsg;
if ( geomEngineA->overlaps( layerFeatureB.geometry().constGet(), &errMsg ) ) if ( geomEngineA->overlaps( layerFeatureB.geometry().constGet(), &errMsg ) )
{ {
QgsAbstractGeometry *interGeom = geomEngineA->intersection( layerFeatureB.geometry().constGet() ); std::unique_ptr<QgsAbstractGeometry> interGeom( geomEngineA->intersection( layerFeatureB.geometry().constGet() ) );
if ( interGeom && !interGeom->isEmpty() ) if ( interGeom && !interGeom->isEmpty() )
{ {
QgsGeometryCheckerUtils::filter1DTypes( interGeom ); QgsGeometryCheckerUtils::filter1DTypes( interGeom.get() );
for ( int iPart = 0, nParts = interGeom->partCount(); iPart < nParts; ++iPart ) for ( int iPart = 0, nParts = interGeom->partCount(); iPart < nParts; ++iPart )
{ {
QgsAbstractGeometry *interPart = QgsGeometryCheckerUtils::getGeomPart( interGeom, iPart ); QgsAbstractGeometry *interPart = QgsGeometryCheckerUtils::getGeomPart( interGeom.get(), iPart );
double area = interPart->area(); double area = interPart->area();
if ( area > mContext->reducedTolerance && area < mOverlapThresholdMapUnits ) if ( area > mContext->reducedTolerance && ( area < mOverlapThresholdMapUnits || mOverlapThresholdMapUnits == 0.0 ) )
{ {
errors.append( new QgsGeometryOverlapCheckError( this, layerFeatureA, QgsGeometry( interPart->clone() ), interPart->centroid(), area, layerFeatureB ) ); errors.append( new QgsGeometryOverlapCheckError( this, layerFeatureA, QgsGeometry( interPart->clone() ), interPart->centroid(), area, layerFeatureB ) );
} }
@ -75,7 +81,6 @@ void QgsGeometryOverlapCheck::collectErrors( const QMap<QString, QgsFeaturePool
{ {
messages.append( tr( "Overlap check between features %1 and %2 %3" ).arg( layerFeatureA.id(), layerFeatureB.id(), errMsg ) ); messages.append( tr( "Overlap check between features %1 and %2 %3" ).arg( layerFeatureA.id(), layerFeatureB.id(), errMsg ) );
} }
delete interGeom;
} }
} }
} }
@ -87,11 +92,11 @@ void QgsGeometryOverlapCheck::fixError( const QMap<QString, QgsFeaturePool *> &f
QgsGeometryOverlapCheckError *overlapError = static_cast<QgsGeometryOverlapCheckError *>( error ); QgsGeometryOverlapCheckError *overlapError = static_cast<QgsGeometryOverlapCheckError *>( error );
QgsFeaturePool *featurePoolA = featurePools[ overlapError->layerId() ]; QgsFeaturePool *featurePoolA = featurePools[ overlapError->layerId() ];
QgsFeaturePool *featurePoolB = featurePools[ overlapError->overlappedFeature().first ]; QgsFeaturePool *featurePoolB = featurePools[ overlapError->overlappedFeature().layerId() ];
QgsFeature featureA; QgsFeature featureA;
QgsFeature featureB; QgsFeature featureB;
if ( !featurePoolA->getFeature( overlapError->featureId(), featureA ) || if ( !featurePoolA->getFeature( overlapError->featureId(), featureA ) ||
!featurePoolB->getFeature( overlapError->overlappedFeature().second, featureB ) ) !featurePoolB->getFeature( overlapError->overlappedFeature().featureId(), featureB ) )
{ {
error->setObsolete(); error->setObsolete();
return; return;
@ -181,7 +186,7 @@ void QgsGeometryOverlapCheck::fixError( const QMap<QString, QgsFeaturePool *> &f
diff2->transform( ct, QgsCoordinateTransform::ReverseTransform ); diff2->transform( ct, QgsCoordinateTransform::ReverseTransform );
featureB.setGeometry( QgsGeometry( std::move( diff2 ) ) ); featureB.setGeometry( QgsGeometry( std::move( diff2 ) ) );
changes[overlapError->overlappedFeature().first][featureB.id()].append( Change( ChangeFeature, ChangeChanged ) ); changes[overlapError->overlappedFeature().layerId()][featureB.id()].append( Change( ChangeFeature, ChangeChanged ) );
featurePoolB->updateFeature( featureB ); featurePoolB->updateFeature( featureB );
} }
@ -244,15 +249,14 @@ bool QgsGeometryOverlapCheck::factoryIsCompatible( QgsVectorLayer *layer ) SIP_S
QgsGeometryOverlapCheckError::QgsGeometryOverlapCheckError( const QgsGeometryCheck *check, const QgsGeometryCheckerUtils::LayerFeature &layerFeature, const QgsGeometry &geometry, const QgsPointXY &errorLocation, const QVariant &value, const QgsGeometryCheckerUtils::LayerFeature &overlappedFeature ) QgsGeometryOverlapCheckError::QgsGeometryOverlapCheckError( const QgsGeometryCheck *check, const QgsGeometryCheckerUtils::LayerFeature &layerFeature, const QgsGeometry &geometry, const QgsPointXY &errorLocation, const QVariant &value, const QgsGeometryCheckerUtils::LayerFeature &overlappedFeature )
: QgsGeometryCheckError( check, layerFeature.layer()->id(), layerFeature.feature().id(), geometry, errorLocation, QgsVertexId(), value, ValueArea ) : QgsGeometryCheckError( check, layerFeature.layer()->id(), layerFeature.feature().id(), geometry, errorLocation, QgsVertexId(), value, ValueArea )
, mOverlappedFeature( qMakePair( overlappedFeature.layer()->id(), overlappedFeature.feature().id() ) ) , mOverlappedFeature( OverlappedFeature( overlappedFeature.layer(), overlappedFeature.feature().id() ) )
{ {
} }
QString QgsGeometryOverlapCheckError::description() const QString QgsGeometryOverlapCheckError::description() const
{ {
return QCoreApplication::translate( "QgsGeometryTypeCheckError", "Overlap with %1:%2" ).arg( mOverlappedFeature.first, QString::number( mOverlappedFeature.second ) ); return QCoreApplication::translate( "QgsGeometryTypeCheckError", "Overlap with %1 at feature %2" ).arg( mOverlappedFeature.layerName(), QString::number( mOverlappedFeature.featureId() ) );
} }
QgsGeometryCheck::CheckType QgsGeometryOverlapCheck::factoryCheckType() QgsGeometryCheck::CheckType QgsGeometryOverlapCheck::factoryCheckType()

View File

@ -25,13 +25,34 @@
class ANALYSIS_EXPORT QgsGeometryOverlapCheckError : public QgsGeometryCheckError class ANALYSIS_EXPORT QgsGeometryOverlapCheckError : public QgsGeometryCheckError
{ {
public: public:
struct OverlappedFeature
{
public:
OverlappedFeature( QgsVectorLayer *vl, QgsFeatureId fid )
: mLayerId( vl->id() )
, mLayerName( vl->name() )
, mFeatureId( fid )
{}
QString layerId() const {return mLayerId;}
QString layerName() const {return mLayerName;}
QgsFeatureId featureId() const {return mFeatureId;}
bool operator==( const OverlappedFeature &other ) const {return mLayerId == other.layerId() && mFeatureId == other.featureId();}
private:
QString mLayerId;
QString mLayerName;
QgsFeatureId mFeatureId;
};
QgsGeometryOverlapCheckError( const QgsGeometryCheck *check, QgsGeometryOverlapCheckError( const QgsGeometryCheck *check,
const QgsGeometryCheckerUtils::LayerFeature &layerFeature, const QgsGeometryCheckerUtils::LayerFeature &layerFeature,
const QgsGeometry &geometry, const QgsGeometry &geometry,
const QgsPointXY &errorLocation, const QgsPointXY &errorLocation,
const QVariant &value, const QVariant &value,
const QgsGeometryCheckerUtils::LayerFeature &overlappedFeature ); const QgsGeometryCheckerUtils::LayerFeature &overlappedFeature );
const QPair<QString, QgsFeatureId> &overlappedFeature() const { return mOverlappedFeature; } const OverlappedFeature &overlappedFeature() const { return mOverlappedFeature; }
bool isEqual( QgsGeometryCheckError *other ) const override bool isEqual( QgsGeometryCheckError *other ) const override
{ {
@ -56,7 +77,7 @@ class ANALYSIS_EXPORT QgsGeometryOverlapCheckError : public QgsGeometryCheckErro
{ {
return false; return false;
} }
if ( changes.value( mOverlappedFeature.first ).keys().contains( mOverlappedFeature.second ) ) if ( changes.value( mOverlappedFeature.layerId() ).keys().contains( mOverlappedFeature.featureId() ) )
{ {
return false; return false;
} }
@ -66,15 +87,26 @@ class ANALYSIS_EXPORT QgsGeometryOverlapCheckError : public QgsGeometryCheckErro
QString description() const override; QString description() const override;
private: private:
QPair<QString, QgsFeatureId> mOverlappedFeature; OverlappedFeature mOverlappedFeature;
}; };
class ANALYSIS_EXPORT QgsGeometryOverlapCheck : public QgsGeometryCheck class ANALYSIS_EXPORT QgsGeometryOverlapCheck : public QgsGeometryCheck
{ {
public: public:
enum ResolutionMethod { Subtract, NoChange };
/**
* Checks for overlapping polygons.
*
* In \a configuration a maxOverlapArea parameter can be passed. In case this parameter is set
* to something else than 0.0, the error will only be reported if the overlapping area is smaller
* than maxOverlapArea.
* Overlapping areas smaller than the reducedTolerance parameter of the \a context are ignored.
*/
QgsGeometryOverlapCheck( const QgsGeometryCheckContext *context, const QVariantMap &configuration ); QgsGeometryOverlapCheck( const QgsGeometryCheckContext *context, const QVariantMap &configuration );
QList<QgsWkbTypes::GeometryType> compatibleGeometryTypes() const override { return factoryCompatibleGeometryTypes(); } QList<QgsWkbTypes::GeometryType> compatibleGeometryTypes() const override { return factoryCompatibleGeometryTypes(); }
void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback = nullptr, const LayerFeatureIds &ids = LayerFeatureIds() ) const override; void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids = LayerFeatureIds() ) const override;
void fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override; void fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override;
QStringList resolutionMethods() const override; QStringList resolutionMethods() const override;
@ -92,8 +124,6 @@ class ANALYSIS_EXPORT QgsGeometryOverlapCheck : public QgsGeometryCheck
static QgsGeometryCheck::CheckType factoryCheckType() SIP_SKIP; static QgsGeometryCheck::CheckType factoryCheckType() SIP_SKIP;
///@endcond private ///@endcond private
enum ResolutionMethod { Subtract, NoChange };
private: private:
const double mOverlapThresholdMapUnits; const double mOverlapThresholdMapUnits;

View File

@ -29,7 +29,7 @@ class ANALYSIS_EXPORT QgsGeometryPointCoveredByLineCheck : public QgsGeometryChe
static QList<QgsWkbTypes::GeometryType> factoryCompatibleGeometryTypes() {return {QgsWkbTypes::PointGeometry}; } static QList<QgsWkbTypes::GeometryType> factoryCompatibleGeometryTypes() {return {QgsWkbTypes::PointGeometry}; }
static bool factoryIsCompatible( QgsVectorLayer *layer ) SIP_SKIP { return factoryCompatibleGeometryTypes().contains( layer->geometryType() ); } static bool factoryIsCompatible( QgsVectorLayer *layer ) SIP_SKIP { return factoryCompatibleGeometryTypes().contains( layer->geometryType() ); }
QList<QgsWkbTypes::GeometryType> compatibleGeometryTypes() const override { return factoryCompatibleGeometryTypes(); } QList<QgsWkbTypes::GeometryType> compatibleGeometryTypes() const override { return factoryCompatibleGeometryTypes(); }
void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback = nullptr, const LayerFeatureIds &ids = LayerFeatureIds() ) const override; void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids = LayerFeatureIds() ) const override;
void fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override; void fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override;
QStringList resolutionMethods() const override; QStringList resolutionMethods() const override;
static QString factoryDescription() { return tr( "Point not covered by line" ); } static QString factoryDescription() { return tr( "Point not covered by line" ); }

View File

@ -29,7 +29,7 @@ class ANALYSIS_EXPORT QgsGeometryPointInPolygonCheck : public QgsGeometryCheck
static QList<QgsWkbTypes::GeometryType> factoryCompatibleGeometryTypes() {return {QgsWkbTypes::PointGeometry}; } static QList<QgsWkbTypes::GeometryType> factoryCompatibleGeometryTypes() {return {QgsWkbTypes::PointGeometry}; }
static bool factoryIsCompatible( QgsVectorLayer *layer ) SIP_SKIP { return factoryCompatibleGeometryTypes().contains( layer->geometryType() ); } static bool factoryIsCompatible( QgsVectorLayer *layer ) SIP_SKIP { return factoryCompatibleGeometryTypes().contains( layer->geometryType() ); }
QList<QgsWkbTypes::GeometryType> compatibleGeometryTypes() const override { return factoryCompatibleGeometryTypes(); } QList<QgsWkbTypes::GeometryType> compatibleGeometryTypes() const override { return factoryCompatibleGeometryTypes(); }
void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback = nullptr, const LayerFeatureIds &ids = LayerFeatureIds() ) const override; void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids = LayerFeatureIds() ) const override;
void fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override; void fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override;
QStringList resolutionMethods() const override; QStringList resolutionMethods() const override;
static QString factoryDescription() { return tr( "Point not in polygon" ); } static QString factoryDescription() { return tr( "Point not in polygon" ); }

View File

@ -30,7 +30,7 @@ class ANALYSIS_EXPORT QgsGeometrySegmentLengthCheck : public QgsGeometryCheck
static QList<QgsWkbTypes::GeometryType> factoryCompatibleGeometryTypes() {return {QgsWkbTypes::LineGeometry, QgsWkbTypes::PolygonGeometry}; } static QList<QgsWkbTypes::GeometryType> factoryCompatibleGeometryTypes() {return {QgsWkbTypes::LineGeometry, QgsWkbTypes::PolygonGeometry}; }
static bool factoryIsCompatible( QgsVectorLayer *layer ) SIP_SKIP { return factoryCompatibleGeometryTypes().contains( layer->geometryType() ); } static bool factoryIsCompatible( QgsVectorLayer *layer ) SIP_SKIP { return factoryCompatibleGeometryTypes().contains( layer->geometryType() ); }
QList<QgsWkbTypes::GeometryType> compatibleGeometryTypes() const override { return factoryCompatibleGeometryTypes(); } QList<QgsWkbTypes::GeometryType> compatibleGeometryTypes() const override { return factoryCompatibleGeometryTypes(); }
void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback = nullptr, const LayerFeatureIds &ids = LayerFeatureIds() ) const override; void collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids = LayerFeatureIds() ) const override;
void fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override; void fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override;
QStringList resolutionMethods() const override; QStringList resolutionMethods() const override;
static QString factoryDescription() { return tr( "Minimal segment length" ); } static QString factoryDescription() { return tr( "Minimal segment length" ); }

View File

@ -33,6 +33,7 @@ class QgsSingleGeometryCheck;
* *
* An error from a QgsSingleGeometryCheck. * An error from a QgsSingleGeometryCheck.
* *
* \note This class is a technology preview and unstable API.
* \since QGIS 3.4 * \since QGIS 3.4
*/ */
class ANALYSIS_EXPORT QgsSingleGeometryCheckError class ANALYSIS_EXPORT QgsSingleGeometryCheckError
@ -104,6 +105,7 @@ class ANALYSIS_EXPORT QgsSingleGeometryCheckError
* Wraps a QgsSingleGeometryError into a standard QgsGeometryCheckError. * Wraps a QgsSingleGeometryError into a standard QgsGeometryCheckError.
* The single error can be obtained via singleError. * The single error can be obtained via singleError.
* *
* \note This class is a technology preview and unstable API.
* \since QGIS 3.4 * \since QGIS 3.4
*/ */
class ANALYSIS_EXPORT QgsGeometryCheckErrorSingle : public QgsGeometryCheckError class ANALYSIS_EXPORT QgsGeometryCheckErrorSingle : public QgsGeometryCheckError

View File

@ -17,29 +17,14 @@ email : matthias@opengis.ch
#include "qgsthreadingutils.h" #include "qgsthreadingutils.h"
#include "qgsfeaturerequest.h" #include "qgsfeaturerequest.h"
#include "qgsvectorlayer.h"
QgsVectorLayerFeaturePool::QgsVectorLayerFeaturePool( QgsVectorLayer *layer ) QgsVectorLayerFeaturePool::QgsVectorLayerFeaturePool( QgsVectorLayer *layer )
: QgsFeaturePool( layer ) : QObject()
, QgsFeaturePool( layer )
{ {
// Build spatial index connect( layer, &QgsVectorLayer::featureDeleted, this, &QgsVectorLayerFeaturePool::onFeatureDeleted );
QgsFeature feature; connect( layer, &QgsVectorLayer::geometryChanged, this, &QgsVectorLayerFeaturePool::onGeometryChanged );
QgsFeatureRequest req;
QgsFeatureIds featureIds;
QgsFeatureIterator it = layer->getFeatures( req );
while ( it.nextFeature( feature ) )
{
if ( feature.geometry() )
{
insertFeature( feature );
featureIds.insert( feature.id() );
}
else
{
featureIds.remove( feature.id() );
}
}
setFeatureIds( featureIds );
} }
bool QgsVectorLayerFeaturePool::addFeature( QgsFeature &feature, Flags flags ) bool QgsVectorLayerFeaturePool::addFeature( QgsFeature &feature, Flags flags )
@ -147,3 +132,20 @@ void QgsVectorLayerFeaturePool::deleteFeature( QgsFeatureId fid )
} }
} ); } );
} }
void QgsVectorLayerFeaturePool::onGeometryChanged( QgsFeatureId fid, const QgsGeometry &geometry )
{
Q_UNUSED( geometry )
if ( isFeatureCached( fid ) )
{
QgsFeature feature;
getFeature( fid, feature );
refreshCache( feature );
}
}
void QgsVectorLayerFeaturePool::onFeatureDeleted( QgsFeatureId fid )
{
deleteFeature( fid );
}

View File

@ -27,8 +27,10 @@ email : matthias@opengis.ch
* *
* \since QGIS 3.4 * \since QGIS 3.4
*/ */
class ANALYSIS_EXPORT QgsVectorLayerFeaturePool : public QgsFeaturePool class ANALYSIS_EXPORT QgsVectorLayerFeaturePool : public QObject, public QgsFeaturePool
{ {
Q_OBJECT
public: public:
QgsVectorLayerFeaturePool( QgsVectorLayer *layer ); QgsVectorLayerFeaturePool( QgsVectorLayer *layer );
@ -36,6 +38,10 @@ class ANALYSIS_EXPORT QgsVectorLayerFeaturePool : public QgsFeaturePool
bool addFeatures( QgsFeatureList &features, QgsFeatureSink::Flags flags = nullptr ) override; bool addFeatures( QgsFeatureList &features, QgsFeatureSink::Flags flags = nullptr ) override;
void updateFeature( QgsFeature &feature ) override; void updateFeature( QgsFeature &feature ) override;
void deleteFeature( QgsFeatureId fid ) override; void deleteFeature( QgsFeatureId fid ) override;
private slots:
void onGeometryChanged( QgsFeatureId fid, const QgsGeometry &geometry );
void onFeatureDeleted( QgsFeatureId fid );
}; };
#endif // QGSVECTORLAYERFEATUREPOOL_H #endif // QGSVECTORLAYERFEATUREPOOL_H

View File

@ -48,6 +48,9 @@ SET(QGIS_APP_SRCS
qgsdisplayangle.cpp qgsdisplayangle.cpp
qgsfieldcalculator.cpp qgsfieldcalculator.cpp
qgsfirstrundialog.cpp qgsfirstrundialog.cpp
qgsgeometryvalidationservice.cpp
qgsgeometryvalidationdock.cpp
qgsgeometryvalidationmodel.cpp
qgssourcefieldsproperties.cpp qgssourcefieldsproperties.cpp
qgsattributesformproperties.cpp qgsattributesformproperties.cpp
qgsidentifyresultsdialog.cpp qgsidentifyresultsdialog.cpp
@ -276,6 +279,9 @@ SET (QGIS_APP_MOC_HDRS
qgsattributesformproperties.h qgsattributesformproperties.h
qgsformannotationdialog.h qgsformannotationdialog.h
qgsguivectorlayertools.h qgsguivectorlayertools.h
qgsgeometryvalidationservice.h
qgsgeometryvalidationdock.h
qgsgeometryvalidationmodel.h
qgshtmlannotationdialog.h qgshtmlannotationdialog.h
qgsidentifyresultsdialog.h qgsidentifyresultsdialog.h
qgslabelengineconfigdialog.h qgslabelengineconfigdialog.h
@ -628,6 +634,7 @@ INCLUDE_DIRECTORIES(
${CMAKE_SOURCE_DIR}/src/app/dwg ${CMAKE_SOURCE_DIR}/src/app/dwg
${CMAKE_SOURCE_DIR}/src/app/mesh ${CMAKE_SOURCE_DIR}/src/app/mesh
${CMAKE_SOURCE_DIR}/src/app/locator ${CMAKE_SOURCE_DIR}/src/app/locator
${CMAKE_SOURCE_DIR}/src/analysis
${CMAKE_SOURCE_DIR}/src/analysis/raster ${CMAKE_SOURCE_DIR}/src/analysis/raster
${CMAKE_SOURCE_DIR}/src/core ${CMAKE_SOURCE_DIR}/src/core
${CMAKE_SOURCE_DIR}/src/core/annotations ${CMAKE_SOURCE_DIR}/src/core/annotations
@ -692,6 +699,7 @@ INCLUDE_DIRECTORIES(SYSTEM
INCLUDE_DIRECTORIES( INCLUDE_DIRECTORIES(
../analysis/processing ../analysis/processing
../analysis/raster ../analysis/raster
../analysis/vector/geometry_checker
../core ../core
../core/annotations ../core/annotations
../core/auth ../core/auth

View File

@ -79,6 +79,10 @@
#include "qgsziputils.h" #include "qgsziputils.h"
#include "qgsbrowsermodel.h" #include "qgsbrowsermodel.h"
#include "qgsvectorlayerjoinbuffer.h" #include "qgsvectorlayerjoinbuffer.h"
#include "qgsgeometryvalidationservice.h"
#include "qgsanalysis.h"
#include "qgsgeometrycheckregistry.h"
#ifdef HAVE_3D #ifdef HAVE_3D
#include "qgsabstract3drenderer.h" #include "qgsabstract3drenderer.h"
@ -406,6 +410,8 @@ Q_GUI_EXPORT extern int qt_defaultDpiX();
#include "qgsmaptoolrotatelabel.h" #include "qgsmaptoolrotatelabel.h"
#include "qgsmaptoolchangelabelproperties.h" #include "qgsmaptoolchangelabelproperties.h"
#include "qgsmaptoolreverseline.h" #include "qgsmaptoolreverseline.h"
#include "qgsgeometryvalidationmodel.h"
#include "qgsgeometryvalidationdock.h"
#include "vertextool/qgsvertextool.h" #include "vertextool/qgsvertextool.h"
@ -778,7 +784,6 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCh
// what type of project to auto-open // what type of project to auto-open
mProjOpen = settings.value( QStringLiteral( "qgis/projOpenAtLaunch" ), 0 ).toInt(); mProjOpen = settings.value( QStringLiteral( "qgis/projOpenAtLaunch" ), 0 ).toInt();
startProfile( QStringLiteral( "Welcome page" ) ); startProfile( QStringLiteral( "Welcome page" ) );
mWelcomePage = new QgsWelcomePage( skipVersionCheck ); mWelcomePage = new QgsWelcomePage( skipVersionCheck );
connect( mWelcomePage, &QgsWelcomePage::projectRemoved, this, [ this ]( int row ) connect( mWelcomePage, &QgsWelcomePage::projectRemoved, this, [ this ]( int row )
@ -918,6 +923,20 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCh
functionProfile( &QgisApp::initNativeProcessing, this, QStringLiteral( "Initialize native processing" ) ); functionProfile( &QgisApp::initNativeProcessing, this, QStringLiteral( "Initialize native processing" ) );
functionProfile( &QgisApp::initLayouts, this, QStringLiteral( "Initialize layouts support" ) ); functionProfile( &QgisApp::initLayouts, this, QStringLiteral( "Initialize layouts support" ) );
startProfile( QStringLiteral( "Geometry validation" ) );
mGeometryValidationService = qgis::make_unique<QgsGeometryValidationService>( QgsProject::instance() );
mGeometryValidationService->setMessageBar( mInfoBar );
mGeometryValidationDock = new QgsGeometryValidationDock( tr( "Geometry Validation" ), mMapCanvas, this );
mGeometryValidationModel = new QgsGeometryValidationModel( mGeometryValidationService.get(), mGeometryValidationDock );
connect( this, &QgisApp::activeLayerChanged, mGeometryValidationModel, [this]( QgsMapLayer * layer )
{
mGeometryValidationModel->setCurrentLayer( qobject_cast<QgsVectorLayer *>( layer ) );
} );
mGeometryValidationDock->setGeometryValidationModel( mGeometryValidationModel );
mGeometryValidationDock->setGeometryValidationService( mGeometryValidationService.get() );
endProfile();
QgsApplication::annotationRegistry()->addAnnotationType( QgsAnnotationMetadata( QStringLiteral( "FormAnnotationItem" ), &QgsFormAnnotation::create ) ); QgsApplication::annotationRegistry()->addAnnotationType( QgsAnnotationMetadata( QStringLiteral( "FormAnnotationItem" ), &QgsFormAnnotation::create ) );
connect( QgsProject::instance()->annotationManager(), &QgsAnnotationManager::annotationAdded, this, &QgisApp::annotationCreated ); connect( QgsProject::instance()->annotationManager(), &QgsAnnotationManager::annotationAdded, this, &QgisApp::annotationCreated );
@ -1418,6 +1437,7 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCh
{ {
mCentralContainer->setCurrentIndex( 0 ); mCentralContainer->setCurrentIndex( 0 );
} ); } );
mGeometryValidationDock->close();
} // QgisApp ctor } // QgisApp ctor
QgisApp::QgisApp() QgisApp::QgisApp()
@ -6940,11 +6960,18 @@ void QgisApp::labelingFontNotFound( QgsVectorLayer *vlayer, const QString &fontf
void QgisApp::commitError( QgsVectorLayer *vlayer ) void QgisApp::commitError( QgsVectorLayer *vlayer )
{ {
const QStringList commitErrors = vlayer->commitErrors();
if ( !vlayer->allowCommit() && commitErrors.empty() )
{
QgsMessageLog::logMessage( tr( "Could not save changes. Geometry validation failed." ) );
return;
}
QgsMessageViewer *mv = new QgsMessageViewer(); QgsMessageViewer *mv = new QgsMessageViewer();
mv->setWindowTitle( tr( "Commit Errors" ) ); mv->setWindowTitle( tr( "Commit Errors" ) );
mv->setMessageAsPlainText( tr( "Could not commit changes to layer %1" ).arg( vlayer->name() ) mv->setMessageAsPlainText( tr( "Could not commit changes to layer %1" ).arg( vlayer->name() )
+ "\n\n" + "\n\n"
+ tr( "Errors: %1\n" ).arg( vlayer->commitErrors().join( QStringLiteral( "\n " ) ) ) + tr( "Errors: %1\n" ).arg( commitErrors.join( QStringLiteral( "\n " ) ) )
); );
QToolButton *showMore = new QToolButton(); QToolButton *showMore = new QToolButton();

View File

@ -101,6 +101,9 @@ class QgsVectorLayerTools;
class QgsWelcomePage; class QgsWelcomePage;
class QgsOptionsWidgetFactory; class QgsOptionsWidgetFactory;
class QgsStatusBar; class QgsStatusBar;
class QgsGeometryValidationService;
class QgsGeometryValidationDock;
class QgsGeometryValidationModel;
class QgsUserProfileManagerWidgetFactory; class QgsUserProfileManagerWidgetFactory;
class Qgs3DMapCanvasDockWidget; class Qgs3DMapCanvasDockWidget;
@ -114,6 +117,7 @@ class QgsAdvancedDigitizingDockWidget;
class QgsGpsInformationWidget; class QgsGpsInformationWidget;
class QgsStatisticalSummaryDockWidget; class QgsStatisticalSummaryDockWidget;
class QgsMapCanvasTracer; class QgsMapCanvasTracer;
class QgsDecorationItem; class QgsDecorationItem;
class QgsMessageLogViewer; class QgsMessageLogViewer;
class QgsMessageBar; class QgsMessageBar;
@ -2305,6 +2309,10 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
//! True if we are blocking the activeLayerChanged signal from being emitted //! True if we are blocking the activeLayerChanged signal from being emitted
bool mBlockActiveLayerChanged = false; bool mBlockActiveLayerChanged = false;
std::unique_ptr<QgsGeometryValidationService> mGeometryValidationService;
QgsGeometryValidationModel *mGeometryValidationModel = nullptr;
QgsGeometryValidationDock *mGeometryValidationDock = nullptr;
friend class TestQgisAppPython; friend class TestQgisAppPython;
friend class QgisAppInterface; friend class QgisAppInterface;
friend class QgsAppScreenShots; friend class QgsAppScreenShots;

View File

@ -0,0 +1,351 @@
/***************************************************************************
qgsgeometryvalidationdock.cpp
--------------------------------------
Date : 7.9.2018
Copyright : (C) 2018 by Matthias Kuhn
email : matthias@opengis.ch
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include <QButtonGroup>
#include <QToolButton>
#include "qgsgeometryvalidationdock.h"
#include "qgsgeometryvalidationmodel.h"
#include "qgsgeometryvalidationservice.h"
#include "qgsmapcanvas.h"
#include "qgsrubberband.h"
#include "qgsvectorlayer.h"
#include "qgsgeometrycheck.h"
#include "qgsgeometrycheckerror.h"
#include "qgsanalysis.h"
#include "qgsgeometrycheckregistry.h"
#include "qgsgeometryoptions.h"
#include "qgsgeometrycheckfactory.h"
#include "qgisapp.h"
QgsGeometryValidationDock::QgsGeometryValidationDock( const QString &title, QgsMapCanvas *mapCanvas, QgisApp *parent, Qt::WindowFlags flags )
: QgsDockWidget( title, parent, flags )
, mMapCanvas( mapCanvas )
, mQgisApp( parent )
{
setupUi( this );
mProblemDescriptionLabel->setStyleSheet( QStringLiteral( "font: bold" ) );
mErrorListView->setAlternatingRowColors( true );
connect( mNextButton, &QToolButton::clicked, this, &QgsGeometryValidationDock::gotoNextError );
connect( mPreviousButton, &QToolButton::clicked, this, &QgsGeometryValidationDock::gotoPreviousError );
connect( mZoomToProblemButton, &QToolButton::clicked, this, &QgsGeometryValidationDock::zoomToProblem );
connect( mZoomToFeatureButton, &QToolButton::clicked, this, &QgsGeometryValidationDock::zoomToFeature );
connect( mMapCanvas, &QgsMapCanvas::currentLayerChanged, this, &QgsGeometryValidationDock::onCurrentLayerChanged );
connect( mMapCanvas, &QgsMapCanvas::currentLayerChanged, this, &QgsGeometryValidationDock::updateLayerTransform );
connect( mMapCanvas, &QgsMapCanvas::destinationCrsChanged, this, &QgsGeometryValidationDock::updateLayerTransform );
connect( mMapCanvas, &QgsMapCanvas::transformContextChanged, this, &QgsGeometryValidationDock::updateLayerTransform );
connect( mTopologyChecksPendingButton, &QToolButton::clicked, this, &QgsGeometryValidationDock::triggerTopologyChecks );
mFeatureRubberband = new QgsRubberBand( mMapCanvas );
mErrorRubberband = new QgsRubberBand( mMapCanvas );
mErrorLocationRubberband = new QgsRubberBand( mMapCanvas );
double scaleFactor = mMapCanvas->fontMetrics().xHeight() * .4;
mFeatureRubberband->setWidth( scaleFactor );
mFeatureRubberband->setStrokeColor( QColor( 100, 255, 100, 100 ) );
mErrorRubberband->setColor( QColor( 255, 238, 88, 255 ) );
mErrorRubberband->setWidth( scaleFactor );
mErrorLocationRubberband->setIcon( QgsRubberBand::ICON_X );
mErrorLocationRubberband->setWidth( scaleFactor );
mErrorLocationRubberband->setIconSize( scaleFactor * 5 );
mErrorLocationRubberband->setColor( QColor( 50, 255, 50, 255 ) );
mProblemDetailWidget->setVisible( false );
}
QgsGeometryValidationModel *QgsGeometryValidationDock::geometryValidationModel() const
{
return mGeometryValidationModel;
}
void QgsGeometryValidationDock::setGeometryValidationModel( QgsGeometryValidationModel *geometryValidationModel )
{
mGeometryValidationModel = geometryValidationModel;
mErrorListView->setModel( mGeometryValidationModel );
connect( mErrorListView->selectionModel(), &QItemSelectionModel::currentChanged, this, &QgsGeometryValidationDock::onCurrentErrorChanged );
connect( mGeometryValidationModel, &QgsGeometryValidationModel::dataChanged, this, &QgsGeometryValidationDock::onDataChanged );
connect( mGeometryValidationModel, &QgsGeometryValidationModel::rowsRemoved, this, &QgsGeometryValidationDock::updateCurrentError );
connect( mGeometryValidationModel, &QgsGeometryValidationModel::rowsInserted, this, &QgsGeometryValidationDock::onRowsInserted );
}
void QgsGeometryValidationDock::gotoNextError()
{
QItemSelectionModel *selectionModel = mErrorListView->selectionModel();
selectionModel->setCurrentIndex( mGeometryValidationModel->index( selectionModel->currentIndex().row() + 1, 0, QModelIndex() ), QItemSelectionModel::ClearAndSelect );
}
void QgsGeometryValidationDock::gotoPreviousError()
{
QItemSelectionModel *selectionModel = mErrorListView->selectionModel();
selectionModel->setCurrentIndex( mGeometryValidationModel->index( selectionModel->currentIndex().row() - 1, 0, QModelIndex() ), QItemSelectionModel::ClearAndSelect );
}
void QgsGeometryValidationDock::zoomToProblem()
{
mLastZoomToAction = ZoomToProblem;
if ( !currentIndex().isValid() )
return;
QgsRectangle problemExtent = currentIndex().data( QgsGeometryValidationModel::ProblemExtentRole ).value<QgsRectangle>();
QgsRectangle mapExtent = mLayerTransform.transform( problemExtent );
mMapCanvas->zoomToFeatureExtent( mapExtent );
}
void QgsGeometryValidationDock::zoomToFeature()
{
mLastZoomToAction = ZoomToFeature;
if ( !currentIndex().isValid() )
return;
QgsRectangle featureExtent = currentIndex().data( QgsGeometryValidationModel::FeatureExtentRole ).value<QgsRectangle>();
if ( !featureExtent.isEmpty() )
{
QgsRectangle mapExtent = mLayerTransform.transform( featureExtent );
mMapCanvas->zoomToFeatureExtent( mapExtent );
}
}
void QgsGeometryValidationDock::triggerTopologyChecks()
{
QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( mMapCanvas->currentLayer() );
if ( layer )
mGeometryValidationService->triggerTopologyChecks( layer );
}
void QgsGeometryValidationDock::updateLayerTransform()
{
if ( !mMapCanvas->currentLayer() )
return;
mLayerTransform = QgsCoordinateTransform( mMapCanvas->currentLayer()->crs(), mMapCanvas->mapSettings().destinationCrs(), mMapCanvas->mapSettings().transformContext() );
}
void QgsGeometryValidationDock::onDataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles )
{
Q_UNUSED( bottomRight )
Q_UNUSED( roles )
if ( currentIndex() == topLeft )
updateCurrentError();
}
void QgsGeometryValidationDock::onRowsInserted()
{
if ( !isVisible() )
{
mQgisApp->addDockWidget( Qt::RightDockWidgetArea, this );
}
setUserVisible( true );
}
QgsGeometryValidationService *QgsGeometryValidationDock::geometryValidationService() const
{
return mGeometryValidationService;
}
void QgsGeometryValidationDock::setGeometryValidationService( QgsGeometryValidationService *geometryValidationService )
{
mGeometryValidationService = geometryValidationService;
}
void QgsGeometryValidationDock::updateCurrentError()
{
if ( mGeometryValidationModel->rowCount() == 0 )
mErrorListView->selectionModel()->clearCurrentIndex();
mFeatureRubberband->hide();
mFeatureRubberband->update();
mErrorRubberband->hide();
mErrorRubberband->update();
mErrorLocationRubberband->hide();
mErrorLocationRubberband->update();
onCurrentErrorChanged( currentIndex(), QModelIndex() );
}
QModelIndex QgsGeometryValidationDock::currentIndex() const
{
if ( !mGeometryValidationModel->rowCount() )
return QModelIndex();
else
return mErrorListView->selectionModel()->currentIndex();
}
void QgsGeometryValidationDock::onCurrentErrorChanged( const QModelIndex &current, const QModelIndex &previous )
{
Q_UNUSED( previous )
mProblemDetailWidget->setVisible( current.isValid() );
mNextButton->setEnabled( current.isValid() && current.row() < mGeometryValidationModel->rowCount() - 1 );
mPreviousButton->setEnabled( current.isValid() && current.row() > 0 );
mProblemDetailWidget->setVisible( current.isValid() );
if ( !current.isValid() )
return;
mProblemDescriptionLabel->setText( current.data( QgsGeometryValidationModel::DetailsRole ).toString() );
QgsGeometryCheckError *error = current.data( QgsGeometryValidationModel::GeometryCheckErrorRole ).value<QgsGeometryCheckError *>();
if ( error )
{
while ( QWidget *w = mResolutionWidget->findChild<QWidget *>() )
delete w;
delete mResolutionWidget->layout();
if ( error->status() != QgsGeometryCheckError::StatusFixed )
{
const QStringList resolutionMethods = error->check()->resolutionMethods();
QGridLayout *layout = new QGridLayout( mResolutionWidget );
int resolutionIndex = 0;
for ( const QString &resolutionMethod : resolutionMethods )
{
QToolButton *resolveBtn = new QToolButton( mResolutionWidget );
resolveBtn->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/algorithms/mAlgorithmCheckGeometry.svg" ) ) );
layout->addWidget( resolveBtn, resolutionIndex, 0 );
QLabel *resolveLabel = new QLabel( resolutionMethod, mResolutionWidget );
resolveLabel->setWordWrap( true );
layout->addWidget( resolveLabel, resolutionIndex, 1 );
connect( resolveBtn, &QToolButton::clicked, this, [resolutionIndex, error, this]()
{
mGeometryValidationService->fixError( error, resolutionIndex );
} );
resolutionIndex++;
}
mResolutionWidget->setLayout( layout );
showHighlight( current );
}
}
bool hasFeature = !FID_IS_NULL( current.data( QgsGeometryValidationModel::ErrorFeatureIdRole ) );
mZoomToFeatureButton->setEnabled( hasFeature );
switch ( mLastZoomToAction )
{
case ZoomToProblem:
zoomToProblem();
break;
case ZoomToFeature:
zoomToFeature();
break;
}
}
void QgsGeometryValidationDock::onCurrentLayerChanged( QgsMapLayer *layer )
{
if ( layer == mCurrentLayer )
return;
if ( mCurrentLayer )
{
disconnect( mCurrentLayer, &QgsVectorLayer::editingStarted, this, &QgsGeometryValidationDock::onLayerEditingStatusChanged );
disconnect( mCurrentLayer, &QgsVectorLayer::editingStopped, this, &QgsGeometryValidationDock::onLayerEditingStatusChanged );
disconnect( mCurrentLayer, &QgsVectorLayer::destroyed, this, &QgsGeometryValidationDock::onLayerDestroyed );
}
mCurrentLayer = qobject_cast<QgsVectorLayer *>( layer );
if ( mCurrentLayer )
{
connect( mCurrentLayer, &QgsVectorLayer::editingStarted, this, &QgsGeometryValidationDock::onLayerEditingStatusChanged );
connect( mCurrentLayer, &QgsVectorLayer::editingStopped, this, &QgsGeometryValidationDock::onLayerEditingStatusChanged );
connect( mCurrentLayer, &QgsVectorLayer::destroyed, this, &QgsGeometryValidationDock::onLayerDestroyed );
}
onLayerEditingStatusChanged();
}
void QgsGeometryValidationDock::onLayerEditingStatusChanged()
{
bool enabled = false;
if ( mCurrentLayer && mCurrentLayer->isSpatial() && mCurrentLayer->isEditable() )
{
const QList<QgsGeometryCheckFactory *> topologyCheckFactories = QgsAnalysis::instance()->geometryCheckRegistry()->geometryCheckFactories( mCurrentLayer, QgsGeometryCheck::LayerCheck, QgsGeometryCheck::Flag::AvailableInValidation );
const QStringList activeChecks = mCurrentLayer->geometryOptions()->geometryChecks();
for ( const QgsGeometryCheckFactory *factory : topologyCheckFactories )
{
if ( activeChecks.contains( factory->id() ) )
{
enabled = true;
break;
}
}
}
mTopologyChecksPendingButton->setEnabled( enabled );
}
void QgsGeometryValidationDock::onLayerDestroyed( QObject *layer )
{
if ( layer == mCurrentLayer )
mCurrentLayer = nullptr;
}
void QgsGeometryValidationDock::showHighlight( const QModelIndex &current )
{
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mMapCanvas->currentLayer() );
if ( vlayer )
{
QgsGeometry featureGeometry = current.data( QgsGeometryValidationModel::FeatureGeometryRole ).value<QgsGeometry>();
QgsGeometry errorGeometry = current.data( QgsGeometryValidationModel::ErrorGeometryRole ).value<QgsGeometry>();
QgsPointXY locationGeometry = current.data( QgsGeometryValidationModel::ErrorLocationGeometryRole ).value<QgsPointXY>();
mFeatureRubberband->setToGeometry( featureGeometry );
QPropertyAnimation *featureAnimation = new QPropertyAnimation( mFeatureRubberband, "fillColor" );
featureAnimation->setEasingCurve( QEasingCurve::OutQuad );
connect( featureAnimation, &QPropertyAnimation::finished, featureAnimation, &QPropertyAnimation::deleteLater );
connect( featureAnimation, &QPropertyAnimation::valueChanged, this, [this]
{
mFeatureRubberband->update();
} );
featureAnimation->setDuration( 2000 );
featureAnimation->setStartValue( QColor( 100, 255, 100, 255 ) );
featureAnimation->setEndValue( QColor( 100, 255, 100, 0 ) );
featureAnimation->start();
mErrorRubberband->setToGeometry( errorGeometry );
QPropertyAnimation *errorAnimation = new QPropertyAnimation( mErrorRubberband, "fillColor" );
errorAnimation->setEasingCurve( QEasingCurve::OutQuad );
connect( errorAnimation, &QPropertyAnimation::finished, featureAnimation, &QPropertyAnimation::deleteLater );
connect( errorAnimation, &QPropertyAnimation::valueChanged, this, [this]
{
mErrorRubberband->update();
} );
errorAnimation->setStartValue( QColor( 255, 238, 88, 255 ) );
errorAnimation->setEndValue( QColor( 255, 238, 88, 0 ) );
errorAnimation->setDuration( 2000 );
errorAnimation->start();
mErrorLocationRubberband->setToGeometry( QgsGeometry( qgis::make_unique<QgsPoint>( locationGeometry ) ) );
}
}

View File

@ -0,0 +1,85 @@
/***************************************************************************
qgsgeometryvalidationdock.h
--------------------------------------
Date : 7.9.2018
Copyright : (C) 2018 by Matthias Kuhn
email : matthias@opengis.ch
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSGEOMETRYVALIDATIONPANEL_H
#define QGSGEOMETRYVALIDATIONPANEL_H
#include "ui_qgsgeometryvalidationdockbase.h"
#include "qgsdockwidget.h"
#include "qgscoordinatetransform.h"
class QgsMapCanvas;
class QgsGeometryValidationModel;
class QgsGeometryValidationService;
class QgsRubberBand;
class QgisApp;
class QgsVectorLayer;
/**
* @brief The QgsGeometryValidationDock class
*/
class QgsGeometryValidationDock : public QgsDockWidget, public Ui_QgsGeometryValidationDockBase
{
Q_OBJECT
public:
QgsGeometryValidationDock( const QString &title, QgsMapCanvas *mapCanvas, QgisApp *parent = nullptr, Qt::WindowFlags flags = nullptr );
QgsGeometryValidationModel *geometryValidationModel() const;
void setGeometryValidationModel( QgsGeometryValidationModel *geometryValidationModel );
QgsGeometryValidationService *geometryValidationService() const;
void setGeometryValidationService( QgsGeometryValidationService *geometryValidationService );
private slots:
void updateCurrentError();
void onCurrentErrorChanged( const QModelIndex &current, const QModelIndex &previous );
void onCurrentLayerChanged( QgsMapLayer *layer );
void onLayerEditingStatusChanged();
void onLayerDestroyed( QObject *layer );
void gotoNextError();
void gotoPreviousError();
void zoomToProblem();
void zoomToFeature();
void triggerTopologyChecks();
void updateLayerTransform();
void onDataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles );
void onRowsInserted();
private:
enum ZoomToAction
{
ZoomToFeature,
ZoomToProblem
};
void showHighlight( const QModelIndex &current );
ZoomToAction mLastZoomToAction = ZoomToFeature;
QgsGeometryValidationModel *mGeometryValidationModel = nullptr;
QgsGeometryValidationService *mGeometryValidationService = nullptr;
QButtonGroup *mZoomToButtonGroup = nullptr;
QgsMapCanvas *mMapCanvas = nullptr;
QgisApp *mQgisApp = nullptr;
QgsCoordinateTransform mLayerTransform;
QModelIndex currentIndex() const;
QgsRubberBand *mFeatureRubberband = nullptr;
QgsRubberBand *mErrorRubberband = nullptr;
QgsRubberBand *mErrorLocationRubberband = nullptr;
QgsVectorLayer *mCurrentLayer = nullptr;
};
#endif // QGSGEOMETRYVALIDATIONPANEL_H

View File

@ -0,0 +1,391 @@
/***************************************************************************
qgsgeometryvalidationmodel.cpp
--------------------------------------
Date : 6.9.2018
Copyright : (C) 2018 by Matthias Kuhn
email : matthias@opengis.ch
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsgeometryvalidationmodel.h"
#include "qgsvectorlayer.h"
#include "qgssinglegeometrycheck.h"
#include "qgsfeatureid.h"
#include <QIcon>
QgsGeometryValidationModel::QgsGeometryValidationModel( QgsGeometryValidationService *geometryValidationService, QObject *parent )
: QAbstractItemModel( parent )
, mGeometryValidationService( geometryValidationService )
{
connect( mGeometryValidationService, &QgsGeometryValidationService::geometryCheckCompleted, this, &QgsGeometryValidationModel::onGeometryCheckCompleted );
connect( mGeometryValidationService, &QgsGeometryValidationService::geometryCheckStarted, this, &QgsGeometryValidationModel::onGeometryCheckStarted );
connect( mGeometryValidationService, &QgsGeometryValidationService::topologyChecksUpdated, this, &QgsGeometryValidationModel::onTopologyChecksUpdated );
connect( mGeometryValidationService, &QgsGeometryValidationService::topologyChecksCleared, this, &QgsGeometryValidationModel::onTopologyChecksCleared );
connect( mGeometryValidationService, &QgsGeometryValidationService::topologyErrorUpdated, this, &QgsGeometryValidationModel::onTopologyErrorUpdated );
}
QModelIndex QgsGeometryValidationModel::index( int row, int column, const QModelIndex &parent ) const
{
Q_UNUSED( parent )
return createIndex( row, column );
}
QModelIndex QgsGeometryValidationModel::parent( const QModelIndex &child ) const
{
Q_UNUSED( child )
return QModelIndex();
}
int QgsGeometryValidationModel::rowCount( const QModelIndex &parent ) const
{
Q_UNUSED( parent )
return mErrorStorage.value( mCurrentLayer ).size() + mTopologyErrorStorage.value( mCurrentLayer ).size();
}
int QgsGeometryValidationModel::columnCount( const QModelIndex &parent ) const
{
Q_UNUSED( parent )
return 1;
}
QVariant QgsGeometryValidationModel::data( const QModelIndex &index, int role ) const
{
const auto &layerErrors = mErrorStorage.value( mCurrentLayer );
if ( index.row() >= layerErrors.size() )
{
// Topology error
const auto &topologyErrors = mTopologyErrorStorage.value( mCurrentLayer );
auto topologyError = topologyErrors.at( index.row() - layerErrors.size() );
switch ( role )
{
case Qt::DecorationRole:
if ( topologyError->status() == QgsGeometryCheckError::StatusFixed )
return QgsApplication::getThemeIcon( QStringLiteral( "/algorithms/mAlgorithmCheckGeometry.svg" ) );
else
return QgsApplication::getThemeIcon( QStringLiteral( "/algorithms/mAlgorithmLineIntersections.svg" ) );
case Qt::DisplayRole:
case DetailsRole:
{
const QgsFeatureId fid = topologyError->featureId();
const QgsFeature feature = getFeature( fid );
mExpressionContext.setFeature( feature );
const QVariant featureTitle = mDisplayExpression.evaluate( &mExpressionContext );
if ( featureTitle.isNull() )
{
return topologyError->description();
}
else
{
return tr( "%1: %2" ).arg( featureTitle.toString(), topologyError->description() );
}
}
case FeatureExtentRole:
{
const QgsFeatureId fid = topologyError->featureId();
if ( FID_IS_NULL( fid ) )
return QgsRectangle();
const QgsFeature feature = getFeature( fid );
return feature.geometry().boundingBox();
}
case ProblemExtentRole:
{
return topologyError->affectedAreaBBox();
}
case ErrorGeometryRole:
{
return topologyError->geometry();
}
case ErrorFeatureIdRole:
{
return topologyError->featureId();
}
case FeatureGeometryRole:
{
const QgsFeatureId fid = topologyError->featureId();
if ( !FID_IS_NULL( fid ) )
{
const QgsFeature feature = getFeature( fid );
return feature.geometry();
}
return QgsGeometry();
}
case ErrorLocationGeometryRole:
{
return topologyError->location();
}
case GeometryCheckErrorRole:
{
return QVariant::fromValue<QgsGeometryCheckError *>( topologyError.get() );
}
}
}
else
{
// Geometry error
const auto &featureItem = layerErrors.at( index.row() );
switch ( role )
{
case Qt::DisplayRole:
{
QgsFeature feature = getFeature( featureItem.fid );
mExpressionContext.setFeature( feature );
QString featureTitle = mDisplayExpression.evaluate( &mExpressionContext ).toString();
if ( featureTitle.isEmpty() )
featureTitle = FID_TO_STRING( featureItem.fid );
if ( featureItem.errors.count() > 1 )
return tr( "%1: %n Errors", "", featureItem.errors.count() ).arg( featureTitle );
else if ( featureItem.errors.count() == 1 )
return tr( "%1: %2" ).arg( featureTitle, featureItem.errors.at( 0 )->description() );
else
return QVariant();
}
case Qt::DecorationRole:
{
#if 0
if ( mGeometryValidationService->validationActive( mCurrentLayer, featureItem.fid ) )
return QgsApplication::getThemeIcon( "/mActionTracing.svg" );
else
return QVariant();
#endif
break;
}
case GeometryCheckErrorRole:
{
// Not (yet?) used
break;
}
case ErrorFeatureIdRole:
{
return featureItem.fid;
}
case FeatureExtentRole:
{
return getFeature( featureItem.fid ).geometry().boundingBox();
}
case ErrorLocationGeometryRole:
{
QgsSingleGeometryCheckError *error = featureItem.errors.first().get();
return error->errorLocation();
}
case ProblemExtentRole:
{
QgsSingleGeometryCheckError *error = featureItem.errors.first().get();
return error->errorLocation().boundingBox();
}
case DetailsRole:
{
QStringList details;
for ( const std::shared_ptr<QgsSingleGeometryCheckError> &error : qgis::as_const( featureItem.errors ) )
{
details << error->description();
}
return details.join( '\n' );
}
default:
return QVariant();
}
}
return QVariant();
}
QgsVectorLayer *QgsGeometryValidationModel::currentLayer() const
{
return mCurrentLayer;
}
void QgsGeometryValidationModel::setCurrentLayer( QgsVectorLayer *currentLayer )
{
if ( mCurrentLayer == currentLayer )
return;
beginResetModel();
mCurrentLayer = currentLayer;
mCachedFeature.setValid( false );
if ( mCurrentLayer )
{
mDisplayExpression = mCurrentLayer ? mCurrentLayer->displayExpression() : QString();
mExpressionContext = QgsExpressionContext( QgsExpressionContextUtils::globalProjectLayerScopes( mCurrentLayer ) );
mDisplayExpression.prepare( &mExpressionContext );
mRequiredAttributes = mDisplayExpression.referencedColumns().toList();
}
else
{
mDisplayExpression = QString();
mExpressionContext = QgsExpressionContext();
}
endResetModel();
}
void QgsGeometryValidationModel::onGeometryCheckCompleted( QgsVectorLayer *layer, QgsFeatureId fid, const QList<std::shared_ptr<QgsSingleGeometryCheckError>> &errors )
{
auto &layerErrors = mErrorStorage[layer];
int featureIdx = errorsForFeature( layer, fid );
// The last check for this feature finished: remove
if ( featureIdx > -1 && errors.empty() ) // && !mGeometryValidationService->validationActive( layer, fid ) )
{
if ( mCurrentLayer == layer )
beginRemoveRows( QModelIndex(), featureIdx, featureIdx );
layerErrors.removeAt( featureIdx );
if ( mCurrentLayer == layer )
endRemoveRows();
}
else if ( !errors.empty() )
{
// a new or updated feature
if ( featureIdx == -1 )
{
featureIdx = layerErrors.count();
if ( mCurrentLayer == layer )
beginInsertRows( QModelIndex(), featureIdx, featureIdx );
layerErrors << FeatureErrors( fid );
if ( mCurrentLayer == layer )
endInsertRows();
}
auto &featureItem = layerErrors[featureIdx];
featureItem.errors.append( errors );
if ( mCurrentLayer == layer )
{
QModelIndex modelIndex = index( featureIdx, 0, QModelIndex() );
emit dataChanged( modelIndex, modelIndex );
}
}
}
void QgsGeometryValidationModel::onGeometryCheckStarted( QgsVectorLayer *layer, QgsFeatureId fid )
{
auto &layerErrors = mErrorStorage[layer];
int featureIdx = errorsForFeature( layer, fid );
if ( featureIdx != -1 )
{
auto &featureItem = layerErrors[featureIdx];
featureItem.errors.clear();
if ( mCurrentLayer == layer )
{
QModelIndex modelIndex = index( featureIdx, 0, QModelIndex() );
emit dataChanged( modelIndex, modelIndex );
}
}
}
void QgsGeometryValidationModel::onTopologyChecksUpdated( QgsVectorLayer *layer, const QList<std::shared_ptr<QgsGeometryCheckError> > &errors )
{
if ( errors.empty() )
return;
auto &topologyLayerErrors = mTopologyErrorStorage[layer];
if ( layer == currentLayer() )
{
const int oldRowCount = rowCount();
beginInsertRows( QModelIndex(), oldRowCount, oldRowCount + errors.size() );
}
topologyLayerErrors.append( errors );
if ( layer == currentLayer() )
{
endInsertRows();
}
}
void QgsGeometryValidationModel::onTopologyChecksCleared( QgsVectorLayer *layer )
{
auto &topologyLayerErrors = mTopologyErrorStorage[layer];
if ( topologyLayerErrors.empty() )
return;
if ( layer == currentLayer() )
{
beginRemoveRows( QModelIndex(), mErrorStorage.size(), rowCount() - 1 );
}
topologyLayerErrors.clear();
if ( layer == currentLayer() )
{
endRemoveRows();
}
}
void QgsGeometryValidationModel::onTopologyErrorUpdated( QgsVectorLayer *layer, QgsGeometryCheckError *error )
{
if ( layer == mCurrentLayer )
{
int i = 0;
const auto &errors = mTopologyErrorStorage[layer];
for ( const auto &currentError : errors )
{
if ( currentError.get() == error )
{
QModelIndex idx = index( i, 0, QModelIndex() );
emit dataChanged( idx, idx );
}
++i;
}
}
}
int QgsGeometryValidationModel::errorsForFeature( QgsVectorLayer *layer, QgsFeatureId fid )
{
const auto &layerErrors = mErrorStorage[layer];
int idx = 0;
for ( const auto &feature : layerErrors )
{
if ( feature.fid == fid )
return idx;
idx++;
}
return -1;
}
QgsFeature QgsGeometryValidationModel::getFeature( QgsFeatureId fid ) const
{
if ( fid != mCachedFeature.id() || !mCachedFeature.isValid() )
{
QgsFeatureRequest request;
request.setFilterFid( fid );
request.setSubsetOfAttributes( mRequiredAttributes, mCurrentLayer->fields() );
mCachedFeature = mCurrentLayer->getFeature( fid );
}
return mCachedFeature;
}

View File

@ -0,0 +1,90 @@
/***************************************************************************
qgsgeometryvalidationmodel.h
--------------------------------------
Date : 6.9.2018
Copyright : (C) 2018 by Matthias Kuhn
email : matthias@opengis.ch
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSGEOMETRYVALIDATIONMODEL_H
#define QGSGEOMETRYVALIDATIONMODEL_H
#include <QAbstractItemModel>
#include "qgsgeometryvalidationservice.h"
#include "qgsexpression.h"
#include "qgsexpressioncontext.h"
class QgsGeometryValidationModel : public QAbstractItemModel
{
Q_OBJECT
public:
enum Roles
{
FeatureExtentRole = Qt::UserRole,
ProblemExtentRole,
ErrorGeometryRole,
ErrorFeatureIdRole,
FeatureGeometryRole,
ErrorLocationGeometryRole,
GeometryCheckErrorRole,
DetailsRole
};
QgsGeometryValidationModel( QgsGeometryValidationService *geometryValidationService, QObject *parent = nullptr );
QModelIndex index( int row, int column, const QModelIndex &parent ) const override;
QModelIndex parent( const QModelIndex &child ) const override;
int rowCount( const QModelIndex &parent = QModelIndex() ) const override;
int columnCount( const QModelIndex &parent ) const override;
QVariant data( const QModelIndex &index, int role ) const override;
QgsVectorLayer *currentLayer() const;
public slots:
void setCurrentLayer( QgsVectorLayer *currentLayer );
private slots:
void onGeometryCheckCompleted( QgsVectorLayer *layer, QgsFeatureId fid, const QList<std::shared_ptr<QgsSingleGeometryCheckError> > &errors );
void onGeometryCheckStarted( QgsVectorLayer *layer, QgsFeatureId fid );
void onTopologyChecksUpdated( QgsVectorLayer *layer, const QList<std::shared_ptr<QgsGeometryCheckError> > &errors );
void onTopologyChecksCleared( QgsVectorLayer *layer );
void onTopologyErrorUpdated( QgsVectorLayer *layer, QgsGeometryCheckError *error );
private:
struct FeatureErrors
{
FeatureErrors() = default;
FeatureErrors( QgsFeatureId fid )
: fid( fid )
{}
QgsFeatureId fid = FID_NULL;
QList<std::shared_ptr<QgsSingleGeometryCheckError>> errors;
};
int errorsForFeature( QgsVectorLayer *layer, QgsFeatureId fid );
QgsFeature getFeature( QgsFeatureId fid ) const;
QgsGeometryValidationService *mGeometryValidationService = nullptr;
QgsVectorLayer *mCurrentLayer = nullptr;
mutable QgsExpression mDisplayExpression;
mutable QStringList mRequiredAttributes;
mutable QgsExpressionContext mExpressionContext;
QMap<QgsVectorLayer *, QList< FeatureErrors > > mErrorStorage;
QMap<QgsVectorLayer *, QList< std::shared_ptr< QgsGeometryCheckError > > > mTopologyErrorStorage;
mutable QgsFeature mCachedFeature;
};
#endif // QGSGEOMETRYVALIDATIONMODEL_H

View File

@ -0,0 +1,460 @@
/***************************************************************************
qgsgeometryvalidationservice.cpp
--------------------------------------
Date : 7.9.2018
Copyright : (C) 2018 by Matthias Kuhn
email : matthias@opengis.ch
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsgeometryvalidationservice.h"
#include "qgsproject.h"
#include "qgsvectorlayer.h"
#include "qgsgeometryoptions.h"
#include "qgsanalysis.h"
#include "qgsgeometrycheckregistry.h"
#include "qgsgeometrycheckfactory.h"
#include "qgsvectorlayereditbuffer.h"
#include "qgsvectorlayerfeaturepool.h"
#include "qgsfeedback.h"
#include "qgsreadwritelocker.h"
#include "qgsmessagebar.h"
#include "qgsmessagebaritem.h"
#include <QtConcurrent>
#include <QFutureWatcher>
QgsGeometryValidationService::QgsGeometryValidationService( QgsProject *project )
: mProject( project )
{
connect( project, &QgsProject::layersAdded, this, &QgsGeometryValidationService::onLayersAdded );
}
void QgsGeometryValidationService::fixError( QgsGeometryCheckError *error, int method )
{
QgsGeometryCheck::Changes changes;
error->check()->fixError( mFeaturePools, error, method, QMap<QString, int>(), changes );
error->setFixed( method );
QgsFeaturePool *featurePool = mFeaturePools.value( error->layerId() );
QgsVectorLayer *layer = nullptr;
if ( featurePool )
layer = featurePool->layer();
else
{
// Some checks don't tell us on which layer they are because they are able to do cross-layer checks.
// E.g. the gap check will report in such a way
for ( auto layerCheck = mLayerChecks.constBegin(); layerCheck != mLayerChecks.constEnd(); ++layerCheck )
{
const QList<std::shared_ptr<QgsGeometryCheckError>> &topologyCheckErrors = layerCheck.value().topologyCheckErrors;
for ( const auto &checkError : topologyCheckErrors )
{
if ( checkError.get() == error )
{
layer = layerCheck.key();
break;
}
}
}
}
if ( layer )
{
layer->triggerRepaint();
emit topologyErrorUpdated( layer, error );
}
}
void QgsGeometryValidationService::onLayersAdded( const QList<QgsMapLayer *> &layers )
{
for ( QgsMapLayer *layer : layers )
{
QgsVectorLayer *vectorLayer = qobject_cast<QgsVectorLayer *>( layer );
if ( vectorLayer )
{
connect( vectorLayer->geometryOptions(), &QgsGeometryOptions::checkConfigurationChanged, this, [this, vectorLayer]()
{
enableLayerChecks( vectorLayer );
} );
connect( vectorLayer, &QgsVectorLayer::destroyed, this, [vectorLayer, this]()
{
cleanupLayerChecks( vectorLayer );
mLayerChecks.remove( vectorLayer );
} );
enableLayerChecks( vectorLayer );
}
}
}
void QgsGeometryValidationService::onFeatureAdded( QgsVectorLayer *layer, QgsFeatureId fid )
{
if ( !mLayerChecks[layer].topologyChecks.empty() )
{
invalidateTopologyChecks( layer );
}
processFeature( layer, fid );
}
void QgsGeometryValidationService::onGeometryChanged( QgsVectorLayer *layer, QgsFeatureId fid, const QgsGeometry &geometry )
{
Q_UNUSED( geometry )
// It would be nice to use the geometry here for the tests.
// But:
// 1. other codepaths to the checks also have no geometry (feature added / feature deleted)
// 2. and looking it up from the edit buffer (in memory) is really fast.
// so in short: it's still a good idea, but not as important as on first thought.
if ( !mLayerChecks[layer].topologyChecks.empty() )
{
invalidateTopologyChecks( layer );
}
processFeature( layer, fid );
}
void QgsGeometryValidationService::onFeatureDeleted( QgsVectorLayer *layer, QgsFeatureId fid )
{
if ( !mLayerChecks[layer].topologyChecks.empty() )
{
invalidateTopologyChecks( layer );
}
mLayerChecks[layer].singleFeatureCheckErrors.remove( fid );
// There should be no geometry errors on a non-existent feature, right?
emit geometryCheckCompleted( layer, fid, QList<std::shared_ptr<QgsSingleGeometryCheckError>>() );
}
void QgsGeometryValidationService::onBeforeCommitChanges( QgsVectorLayer *layer )
{
if ( !mLayerChecks[layer].topologyChecks.empty() ) // TODO && topologyChecks not fulfilled
{
if ( !layer->allowCommit() )
{
showMessage( tr( "Running geometry validation checks before saving…" ) );
}
mLayerChecks[layer].commitPending = true;
triggerTopologyChecks( layer );
}
}
void QgsGeometryValidationService::onEditingStopped( QgsVectorLayer *layer )
{
cancelTopologyCheck( layer );
clearTopologyChecks( layer );
}
void QgsGeometryValidationService::showMessage( const QString &message )
{
mMessageBar->popWidget( mMessageBarItem );
mMessageBarItem = QgsMessageBar::createMessage( tr( "Geometry Validation" ), message );
mMessageBarItem->setDuration( 5 );
mMessageBar->pushItem( mMessageBarItem );
}
void QgsGeometryValidationService::cleanupLayerChecks( QgsVectorLayer *layer )
{
if ( !mLayerChecks.contains( layer ) )
return;
VectorLayerCheckInformation &checkInformation = mLayerChecks[layer];
cancelTopologyCheck( layer );
qDeleteAll( checkInformation.singleFeatureChecks );
qDeleteAll( checkInformation.topologyChecks );
checkInformation.context.reset();
}
void QgsGeometryValidationService::enableLayerChecks( QgsVectorLayer *layer )
{
if ( layer->geometryOptions()->geometryChecks().empty() && !mLayerChecks.contains( layer ) )
return;
VectorLayerCheckInformation &checkInformation = mLayerChecks[layer];
cleanupLayerChecks( layer );
if ( layer->geometryOptions()->geometryChecks().empty() )
{
for ( QMetaObject::Connection connection : qgis::as_const( checkInformation.connections ) )
{
disconnect( connection );
}
checkInformation.connections.clear();
return;
}
int precision = 0;
if ( layer->geometryOptions()->geometryPrecision() == 0 )
precision = 8;
else
{
precision = log10( layer->geometryOptions()->geometryPrecision() ) * -1;
if ( precision < 1 || precision > 13 )
precision = 8;
}
checkInformation.context = qgis::make_unique<QgsGeometryCheckContext>( precision, mProject->crs(), mProject->transformContext() );
QList<QgsGeometryCheck *> layerChecks;
QgsGeometryCheckRegistry *checkRegistry = QgsAnalysis::instance()->geometryCheckRegistry();
const QStringList activeChecks = layer->geometryOptions()->geometryChecks();
const QList<QgsGeometryCheckFactory *> singleCheckFactories = checkRegistry->geometryCheckFactories( layer, QgsGeometryCheck::FeatureNodeCheck, QgsGeometryCheck::AvailableInValidation );
for ( QgsGeometryCheckFactory *factory : singleCheckFactories )
{
const QString checkId = factory->id();
if ( activeChecks.contains( checkId ) )
{
const QVariantMap checkConfiguration = layer->geometryOptions()->checkConfiguration( checkId );
layerChecks.append( factory->createGeometryCheck( checkInformation.context.get(), checkConfiguration ) );
}
}
QList<QgsSingleGeometryCheck *> singleGeometryChecks;
for ( QgsGeometryCheck *check : qgis::as_const( layerChecks ) )
{
Q_ASSERT( dynamic_cast<QgsSingleGeometryCheck *>( check ) );
singleGeometryChecks.append( dynamic_cast<QgsSingleGeometryCheck *>( check ) );
}
checkInformation.singleFeatureChecks = singleGeometryChecks;
// Topology checks
QList<QgsGeometryCheck *> topologyChecks;
const QList<QgsGeometryCheckFactory *> topologyCheckFactories = checkRegistry->geometryCheckFactories( layer, QgsGeometryCheck::LayerCheck, QgsGeometryCheck::AvailableInValidation );
for ( QgsGeometryCheckFactory *factory : topologyCheckFactories )
{
const QString checkId = factory->id();
if ( activeChecks.contains( checkId ) )
{
const QVariantMap checkConfiguration = layer->geometryOptions()->checkConfiguration( checkId );
topologyChecks.append( factory->createGeometryCheck( checkInformation.context.get(), checkConfiguration ) );
}
}
checkInformation.topologyChecks = topologyChecks;
if ( checkInformation.connections.empty() )
{
// Connect to all modifications on a layer that can introduce a geometry or topology error
// Also connect to the beforeCommitChanges signal, so we can trigger topology checks
// We keep all connections around in a list, so if in the future all checks get disabled
// we can kill those connections to be sure the layer does not even get a tiny bit of overhead.
checkInformation.connections
<< connect( layer, &QgsVectorLayer::featureAdded, this, [this, layer]( QgsFeatureId fid )
{
onFeatureAdded( layer, fid );
} );
checkInformation.connections
<< connect( layer, &QgsVectorLayer::geometryChanged, this, [this, layer]( QgsFeatureId fid, const QgsGeometry & geometry )
{
onGeometryChanged( layer, fid, geometry );
} );
checkInformation.connections
<< connect( layer, &QgsVectorLayer::featureDeleted, this, [this, layer]( QgsFeatureId fid )
{
onFeatureDeleted( layer, fid );
} );
checkInformation.connections
<< connect( layer, &QgsVectorLayer::beforeCommitChanges, this, [this, layer]()
{
onBeforeCommitChanges( layer );
} );
checkInformation.connections
<< connect( layer, &QgsVectorLayer::editingStopped, this, [this, layer]()
{
onEditingStopped( layer );
} );
}
}
void QgsGeometryValidationService::cancelTopologyCheck( QgsVectorLayer *layer )
{
QFutureWatcher<void> *futureWatcher = mLayerChecks[layer].topologyCheckFutureWatcher;
if ( futureWatcher )
{
// Make sure no more checks are started first
futureWatcher->cancel();
// Tell all checks to stop asap
const auto feedbacks = mLayerChecks[layer].topologyCheckFeedbacks;
for ( QgsFeedback *feedback : feedbacks )
{
if ( feedback )
feedback->cancel();
}
futureWatcher->waitForFinished();
mLayerChecks[layer].topologyCheckFutureWatcher = nullptr;
}
}
void QgsGeometryValidationService::clearTopologyChecks( QgsVectorLayer *layer )
{
QList<std::shared_ptr<QgsGeometryCheckError>> &allErrors = mLayerChecks[layer].topologyCheckErrors;
allErrors.clear();
emit topologyChecksCleared( layer );
}
void QgsGeometryValidationService::invalidateTopologyChecks( QgsVectorLayer *layer )
{
cancelTopologyCheck( layer );
layer->setAllowCommit( false );
}
void QgsGeometryValidationService::processFeature( QgsVectorLayer *layer, QgsFeatureId fid )
{
emit geometryCheckStarted( layer, fid );
QgsGeometry geometry = layer->getGeometry( fid );
const auto &checks = mLayerChecks[layer].singleFeatureChecks;
mLayerChecks[layer].singleFeatureCheckErrors.remove( fid );
// The errors are going to be sent out via a signal. We cannot keep ownership in here (or can we?)
// nor can we be sure that a single slot is connected to the signal. So make it a shared_ptr.
QList<std::shared_ptr<QgsSingleGeometryCheckError>> allErrors;
for ( QgsSingleGeometryCheck *check : checks )
{
const auto errors = check->processGeometry( geometry );
for ( auto error : errors )
allErrors.append( std::shared_ptr<QgsSingleGeometryCheckError>( error ) );
}
if ( !allErrors.empty() )
mLayerChecks[layer].singleFeatureCheckErrors.insert( fid, allErrors );
emit geometryCheckCompleted( layer, fid, allErrors );
}
void QgsGeometryValidationService::setMessageBar( QgsMessageBar *messageBar )
{
mMessageBar = messageBar;
}
void QgsGeometryValidationService::triggerTopologyChecks( QgsVectorLayer *layer )
{
cancelTopologyCheck( layer );
clearTopologyChecks( layer );
QgsFeatureIds affectedFeatureIds;
if ( layer->editBuffer() )
{
affectedFeatureIds = layer->editBuffer()->changedGeometries().keys().toSet();
affectedFeatureIds.unite( layer->editBuffer()->addedFeatures().keys().toSet() );
}
QgsFeaturePool *featurePool = mFeaturePools.value( layer->id() );
if ( !featurePool )
{
featurePool = new QgsVectorLayerFeaturePool( layer );
mFeaturePools.insert( layer->id(), featurePool );
}
QList<std::shared_ptr<QgsGeometryCheckError>> &allErrors = mLayerChecks[layer].topologyCheckErrors;
QMap<QString, QgsFeatureIds> layerIds;
QgsFeatureRequest request = QgsFeatureRequest( affectedFeatureIds ).setSubsetOfAttributes( QgsAttributeList() );
QgsFeatureIterator it = layer->getFeatures( request );
QgsFeature feature;
QgsRectangle area;
while ( it.nextFeature( feature ) )
{
area.combineExtentWith( feature.geometry().boundingBox() );
}
QgsFeatureRequest areaRequest = QgsFeatureRequest().setFilterRect( area );
QgsFeatureIds checkFeatureIds = featurePool->getFeatures( areaRequest );
layerIds.insert( layer->id(), checkFeatureIds );
QgsGeometryCheck::LayerFeatureIds layerFeatureIds( layerIds );
const QList<QgsGeometryCheck *> checks = mLayerChecks[layer].topologyChecks;
QMap<const QgsGeometryCheck *, QgsFeedback *> feedbacks;
for ( QgsGeometryCheck *check : checks )
feedbacks.insert( check, new QgsFeedback() );
mLayerChecks[layer].topologyCheckFeedbacks = feedbacks.values();
QFuture<void> future = QtConcurrent::map( checks, [&allErrors, layerFeatureIds, layer, feedbacks, this]( const QgsGeometryCheck * check )
{
// Watch out with the layer pointer in here. We are running in a thread, so we do not want to actually use it
// except for using its address to report the error.
QList<QgsGeometryCheckError *> errors;
QStringList messages; // Do we really need these?
QgsFeedback *feedback = feedbacks.value( check );
check->collectErrors( mFeaturePools, errors, messages, feedback, layerFeatureIds );
QgsReadWriteLocker errorLocker( mTopologyCheckLock, QgsReadWriteLocker::Write );
QList<std::shared_ptr<QgsGeometryCheckError> > sharedErrors;
for ( QgsGeometryCheckError *error : errors )
{
sharedErrors.append( std::shared_ptr<QgsGeometryCheckError>( error ) );
}
allErrors.append( sharedErrors );
if ( !feedback->isCanceled() )
emit topologyChecksUpdated( layer, sharedErrors );
errorLocker.unlock();
} );
QFutureWatcher<void> *futureWatcher = new QFutureWatcher<void>();
futureWatcher->setFuture( future );
connect( futureWatcher, &QFutureWatcherBase::finished, this, [&allErrors, layer, feedbacks, futureWatcher, this]()
{
QgsReadWriteLocker errorLocker( mTopologyCheckLock, QgsReadWriteLocker::Read );
layer->setAllowCommit( allErrors.empty() );
errorLocker.unlock();
qDeleteAll( feedbacks.values() );
futureWatcher->deleteLater();
if ( mLayerChecks[layer].topologyCheckFutureWatcher == futureWatcher )
mLayerChecks[layer].topologyCheckFutureWatcher = nullptr;
if ( !allErrors.empty() || !mLayerChecks[layer].singleFeatureCheckErrors.empty() )
{
if ( mLayerChecks[layer].commitPending )
showMessage( tr( "Geometry errors have been found. Please fix the errors before saving the layer." ) );
else
showMessage( tr( "Geometry errors have been found." ) );
}
if ( allErrors.empty() && mLayerChecks[layer].singleFeatureCheckErrors.empty() && mLayerChecks[layer].commitPending )
{
layer->commitChanges();
mMessageBar->popWidget( mMessageBarItem );
mMessageBarItem = nullptr;
}
mLayerChecks[layer].commitPending = false;
} );
mLayerChecks[layer].topologyCheckFutureWatcher = futureWatcher;
}

View File

@ -0,0 +1,127 @@
/***************************************************************************
qgsgeometryvalidationservice.h
--------------------------------------
Date : 7.9.2018
Copyright : (C) 2018 by Matthias Kuhn
email : matthias@opengis.ch
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSGEOMETRYVALIDATIONSERVICE_H
#define QGSGEOMETRYVALIDATIONSERVICE_H
#include <QObject>
#include <QMap>
#include <QFuture>
#include <QReadWriteLock>
#include "qgsfeature.h"
#include "qgsgeometrycheckcontext.h"
class QgsProject;
class QgsMapLayer;
class QgsVectorLayer;
class QgsGeometryCheck;
class QgsSingleGeometryCheck;
class QgsSingleGeometryCheckError;
class QgsGeometryCheckError;
class QgsFeedback;
class QgsFeaturePool;
class QgsMessageBar;
class QgsMessageBarItem;
/**
* This service connects to all layers in a project and triggers validation
* of features whenever they are edited.
* It is responsible for executing validation checks and sending out signals
* upon failure and success.
* It will also make sure, that a layer can only be saved, if all errors have
* been resolved.
*/
class QgsGeometryValidationService : public QObject
{
Q_OBJECT
public:
struct FeatureError
{
FeatureError() = default;
FeatureError( QgsFeatureId fid, QgsGeometry::Error error )
: featureId( fid )
, error( error )
{}
QgsFeatureId featureId = std::numeric_limits<QgsFeatureId>::min();
QgsGeometry::Error error;
};
typedef QList<FeatureError> FeatureErrors;
QgsGeometryValidationService( QgsProject *project );
void fixError( QgsGeometryCheckError *error, int method );
void triggerTopologyChecks( QgsVectorLayer *layer );
void setMessageBar( QgsMessageBar *messageBar );
signals:
void geometryCheckStarted( QgsVectorLayer *layer, QgsFeatureId fid );
void geometryCheckCompleted( QgsVectorLayer *layer, QgsFeatureId fid, const QList<std::shared_ptr<QgsSingleGeometryCheckError>> &errors );
void topologyChecksUpdated( QgsVectorLayer *layer, const QList<std::shared_ptr<QgsGeometryCheckError> > &errors );
void topologyChecksCleared( QgsVectorLayer *layer );
void topologyErrorUpdated( QgsVectorLayer *layer, QgsGeometryCheckError *error );
void warning( const QString &message );
void clearWarning();
private slots:
void onLayersAdded( const QList<QgsMapLayer *> &layers );
void onFeatureAdded( QgsVectorLayer *layer, QgsFeatureId fid );
void onGeometryChanged( QgsVectorLayer *layer, QgsFeatureId fid, const QgsGeometry &geometry );
void onFeatureDeleted( QgsVectorLayer *layer, QgsFeatureId fid );
void onBeforeCommitChanges( QgsVectorLayer *layer );
void onEditingStopped( QgsVectorLayer *layer );
private:
void showMessage( const QString &message );
void cleanupLayerChecks( QgsVectorLayer *layer );
void enableLayerChecks( QgsVectorLayer *layer );
void cancelTopologyCheck( QgsVectorLayer *layer );
void clearTopologyChecks( QgsVectorLayer *layer );
void invalidateTopologyChecks( QgsVectorLayer *layer );
void processFeature( QgsVectorLayer *layer, QgsFeatureId fid );
QgsProject *mProject = nullptr;
struct VectorLayerCheckInformation
{
QList< QgsSingleGeometryCheck * > singleFeatureChecks;
QMap<QgsFeatureId, QList< std::shared_ptr<QgsSingleGeometryCheckError > > > singleFeatureCheckErrors;
QList< QgsGeometryCheck *> topologyChecks;
QFutureWatcher<void> *topologyCheckFutureWatcher = nullptr;
QList<QgsFeedback *> topologyCheckFeedbacks; // will be deleted when topologyCheckFutureWatcher is delteed
QList<std::shared_ptr<QgsGeometryCheckError>> topologyCheckErrors;
QList<QMetaObject::Connection> connections;
std::shared_ptr<QgsGeometryCheckContext> context;
bool commitPending = false;
};
QReadWriteLock mTopologyCheckLock;
QHash<QgsVectorLayer *, VectorLayerCheckInformation> mLayerChecks;
QMap<QString, QgsFeaturePool *> mFeaturePools;
QgsMessageBar *mMessageBar = nullptr;
QgsMessageBarItem *mMessageBarItem = nullptr;
};
#endif // QGSGEOMETRYVALIDATIONSERVICE_H

View File

@ -230,6 +230,7 @@ QVariant QgsMapLayerStyleCategoriesModel::data( const QModelIndex &index, int ro
return QVariant(); return QVariant();
} }
break; break;
} }
return QVariant(); return QVariant();
} }

View File

@ -61,9 +61,13 @@
#include "qgslabelinggui.h" #include "qgslabelinggui.h"
#include "qgssymbollayer.h" #include "qgssymbollayer.h"
#include "qgsgeometryoptions.h" #include "qgsgeometryoptions.h"
#include "qgsgeometrycheckfactory.h"
#include "qgsvectorlayersavestyledialog.h" #include "qgsvectorlayersavestyledialog.h"
#include "qgsvectorlayerloadstyledialog.h" #include "qgsvectorlayerloadstyledialog.h"
#include "qgsmessagebar.h" #include "qgsmessagebar.h"
#include "qgsgeometrycheckregistry.h"
#include "qgsgeometrycheck.h"
#include "qgsanalysis.h"
#include "layertree/qgslayertreelayer.h" #include "layertree/qgslayertreelayer.h"
#include "qgslayertree.h" #include "qgslayertree.h"
@ -400,17 +404,42 @@ QgsVectorLayerProperties::QgsVectorLayerProperties(
if ( mLayer->isSpatial() ) if ( mLayer->isSpatial() )
{ {
mRemoveDuplicateNodesCheckbox->setEnabled( true ); mRemoveDuplicateNodesCheckbox->setEnabled( true );
mGeometryPrecisionSpinBox->setEnabled( true ); mGeometryPrecisionLineEdit->setEnabled( true );
mGeometryPrecisionLineEdit->setValidator( new QDoubleValidator( mGeometryPrecisionLineEdit ) );
mRemoveDuplicateNodesCheckbox->setChecked( mLayer->geometryOptions()->removeDuplicateNodes() ); mRemoveDuplicateNodesCheckbox->setChecked( mLayer->geometryOptions()->removeDuplicateNodes() );
mGeometryPrecisionSpinBox->setValue( mLayer->geometryOptions()->geometryPrecision() ); mGeometryPrecisionLineEdit->setText( QString::number( mLayer->geometryOptions()->geometryPrecision() ) );
mGeometryPrecisionSpinBox->setSuffix( QStringLiteral( " [%1]" ).arg( QgsUnitTypes::toAbbreviatedString( mLayer->crs().mapUnits() ) ) ); mPrecisionUnitsLabel->setText( QStringLiteral( "[%1]" ).arg( QgsUnitTypes::toAbbreviatedString( mLayer->crs().mapUnits() ) ) );
QLayout *geometryCheckLayout = new QVBoxLayout();
const QList<QgsGeometryCheckFactory *> geometryCheckFactories = QgsAnalysis::instance()->geometryCheckRegistry()->geometryCheckFactories( mLayer, QgsGeometryCheck::FeatureNodeCheck, QgsGeometryCheck::Flag::AvailableInValidation );
const QStringList activeChecks = mLayer->geometryOptions()->geometryChecks();
for ( const QgsGeometryCheckFactory *factory : geometryCheckFactories )
{
QCheckBox *cb = new QCheckBox( factory->description() );
cb->setChecked( activeChecks.contains( factory->id() ) );
mGeometryCheckFactoriesGroupBoxes.insert( cb, factory->id() );
geometryCheckLayout->addWidget( cb );
}
mGeometryValidationGroupBox->setLayout( geometryCheckLayout );
QLayout *topologyCheckLayout = new QVBoxLayout();
const QList<QgsGeometryCheckFactory *> topologyCheckFactories = QgsAnalysis::instance()->geometryCheckRegistry()->geometryCheckFactories( mLayer, QgsGeometryCheck::LayerCheck, QgsGeometryCheck::Flag::AvailableInValidation );
for ( const QgsGeometryCheckFactory *factory : topologyCheckFactories )
{
QCheckBox *cb = new QCheckBox( factory->description() );
cb->setChecked( activeChecks.contains( factory->id() ) );
mGeometryCheckFactoriesGroupBoxes.insert( cb, factory->id() );
topologyCheckLayout->addWidget( cb );
}
mTopologyChecksGroupBox->setLayout( topologyCheckLayout );
} }
else else
{ {
mRemoveDuplicateNodesCheckbox->setEnabled( false ); mRemoveDuplicateNodesCheckbox->setEnabled( false );
mGeometryPrecisionSpinBox->setEnabled( false ); mGeometryPrecisionLineEdit->setEnabled( false );
mGeometryAutoFixesGroupBox->setEnabled( false ); mGeometryAutoFixesGroupBox->setEnabled( false );
} }
@ -753,7 +782,16 @@ void QgsVectorLayerProperties::apply()
#endif #endif
mLayer->geometryOptions()->setRemoveDuplicateNodes( mRemoveDuplicateNodesCheckbox->isChecked() ); mLayer->geometryOptions()->setRemoveDuplicateNodes( mRemoveDuplicateNodesCheckbox->isChecked() );
mLayer->geometryOptions()->setGeometryPrecision( mGeometryPrecisionSpinBox->value() ); mLayer->geometryOptions()->setGeometryPrecision( mGeometryPrecisionLineEdit->text().toDouble() );
QStringList activeChecks;
QHash<QCheckBox *, QString>::const_iterator it;
for ( it = mGeometryCheckFactoriesGroupBoxes.constBegin(); it != mGeometryCheckFactoriesGroupBoxes.constEnd(); ++it )
{
if ( it.key()->isChecked() )
activeChecks << it.value();
}
mLayer->geometryOptions()->setGeometryChecks( activeChecks );
mLayer->triggerRepaint(); mLayer->triggerRepaint();
// notify the project we've made a change // notify the project we've made a change

View File

@ -245,6 +245,8 @@ class APP_EXPORT QgsVectorLayerProperties : public QgsOptionsDialogBase, private
QgsVectorLayer3DRendererWidget *mVector3DWidget = nullptr; QgsVectorLayer3DRendererWidget *mVector3DWidget = nullptr;
QHash<QCheckBox *, QString> mGeometryCheckFactoriesGroupBoxes;
private slots: private slots:
void openPanel( QgsPanelWidget *panel ); void openPanel( QgsPanelWidget *panel );

View File

@ -1929,7 +1929,7 @@ bool QgsVertexTool::matchEdgeCenterTest( const QgsPointLocator::Match &m, const
QgsGeometry lineGeom = QgsGeometry::fromPolylineXY( QgsPolylineXY() << p0 << p1 ); QgsGeometry lineGeom = QgsGeometry::fromPolylineXY( QgsPolylineXY() << p0 << p1 );
lineGeom = extentGeom.intersection( lineGeom ); lineGeom = extentGeom.intersection( lineGeom );
QgsPolylineXY polyline = lineGeom.asPolyline(); QgsPolylineXY polyline = lineGeom.asPolyline();
Q_ASSERT( polyline.count() == 2 ); Q_ASSERT_X( polyline.count() == 2, "QgsVertexTool::matchEdgeCenterTest", QgsLineString( polyline ).asWkt().toUtf8().constData() );
p0 = polyline[0]; p0 = polyline[0];
p1 = polyline[1]; p1 = polyline[1];
} }

View File

@ -606,6 +606,7 @@ SET(QGIS_CORE_MOC_HDRS
qgsfiledownloader.h qgsfiledownloader.h
qgsfeaturefiltermodel.h qgsfeaturefiltermodel.h
qgsfeaturefiltermodel_p.h qgsfeaturefiltermodel_p.h
qgsgeometryoptions.h
qgsgeometryvalidator.h qgsgeometryvalidator.h
qgsgml.h qgsgml.h
qgsgmlschema.h qgsgmlschema.h
@ -869,7 +870,6 @@ SET(QGIS_CORE_HDRS
qgsfields.h qgsfields.h
qgsfileutils.h qgsfileutils.h
qgsfontutils.h qgsfontutils.h
qgsgeometryoptions.h
qgsgeometrysimplifier.h qgsgeometrysimplifier.h
qgshistogram.h qgshistogram.h
qgshstoreutils.h qgshstoreutils.h

View File

@ -27,6 +27,7 @@ bool QgsGeometryOptions::removeDuplicateNodes() const
void QgsGeometryOptions::setRemoveDuplicateNodes( bool value ) void QgsGeometryOptions::setRemoveDuplicateNodes( bool value )
{ {
mRemoveDuplicateNodes = value; mRemoveDuplicateNodes = value;
emit removeDuplicateNodesChanged();
} }
double QgsGeometryOptions::geometryPrecision() const double QgsGeometryOptions::geometryPrecision() const
@ -37,6 +38,7 @@ double QgsGeometryOptions::geometryPrecision() const
void QgsGeometryOptions::setGeometryPrecision( double value ) void QgsGeometryOptions::setGeometryPrecision( double value )
{ {
mGeometryPrecision = value; mGeometryPrecision = value;
emit geometryPrecisionChanged();
} }
bool QgsGeometryOptions::isActive() const bool QgsGeometryOptions::isActive() const
@ -53,13 +55,43 @@ void QgsGeometryOptions::apply( QgsGeometry &geometry ) const
geometry.removeDuplicateNodes(); geometry.removeDuplicateNodes();
} }
QStringList QgsGeometryOptions::geometryChecks() const
{
return mGeometryChecks;
}
void QgsGeometryOptions::setGeometryChecks( const QStringList &geometryChecks )
{
mGeometryChecks = geometryChecks;
emit geometryChecksChanged();
}
QVariantMap QgsGeometryOptions::checkConfiguration( const QString &checkId ) const
{
return mCheckConfiguration.value( checkId ).toMap();
}
void QgsGeometryOptions::setCheckConfiguration( const QString &checkId, const QVariantMap &checkConfiguration )
{
mCheckConfiguration[checkId] = checkConfiguration;
emit checkConfigurationChanged();
}
void QgsGeometryOptions::writeXml( QDomNode &node ) const void QgsGeometryOptions::writeXml( QDomNode &node ) const
{ {
QDomElement geometryOptionsElement = node.ownerDocument().createElement( QStringLiteral( "geometryOptions" ) ); QDomDocument doc = node.ownerDocument();
QDomElement geometryOptionsElement = doc.createElement( QStringLiteral( "geometryOptions" ) );
node.appendChild( geometryOptionsElement ); node.appendChild( geometryOptionsElement );
geometryOptionsElement.setAttribute( QStringLiteral( "removeDuplicateNodes" ), mRemoveDuplicateNodes ? 1 : 0 ); geometryOptionsElement.setAttribute( QStringLiteral( "removeDuplicateNodes" ), mRemoveDuplicateNodes ? 1 : 0 );
geometryOptionsElement.setAttribute( QStringLiteral( "geometryPrecision" ), mGeometryPrecision ); geometryOptionsElement.setAttribute( QStringLiteral( "geometryPrecision" ), mGeometryPrecision );
QDomElement activeCheckListElement = QgsXmlUtils::writeVariant( mGeometryChecks, doc );
activeCheckListElement.setTagName( QStringLiteral( "activeChecks" ) );
geometryOptionsElement.appendChild( activeCheckListElement );
QDomElement checkConfigurationElement = QgsXmlUtils::writeVariant( mCheckConfiguration, doc );
checkConfigurationElement.setTagName( QStringLiteral( "checkConfiguration" ) );
geometryOptionsElement.appendChild( checkConfigurationElement );
} }
void QgsGeometryOptions::readXml( const QDomNode &node ) void QgsGeometryOptions::readXml( const QDomNode &node )
@ -67,4 +99,12 @@ void QgsGeometryOptions::readXml( const QDomNode &node )
QDomElement geometryOptionsElement = node.toElement(); QDomElement geometryOptionsElement = node.toElement();
setGeometryPrecision( geometryOptionsElement.attribute( QStringLiteral( "geometryPrecision" ), QStringLiteral( "0.0" ) ).toDouble() ); setGeometryPrecision( geometryOptionsElement.attribute( QStringLiteral( "geometryPrecision" ), QStringLiteral( "0.0" ) ).toDouble() );
setRemoveDuplicateNodes( geometryOptionsElement.attribute( QStringLiteral( "removeDuplicateNodes" ), QStringLiteral( "0" ) ).toInt() == 1 ); setRemoveDuplicateNodes( geometryOptionsElement.attribute( QStringLiteral( "removeDuplicateNodes" ), QStringLiteral( "0" ) ).toInt() == 1 );
QDomElement activeChecksElem = node.namedItem( QStringLiteral( "activeChecks" ) ).toElement();
const QVariant activeChecks = QgsXmlUtils::readVariant( activeChecksElem );
setGeometryChecks( activeChecks.toStringList() );
QDomElement checkConfigurationElem = node.namedItem( QStringLiteral( "checkConfiguration" ) ).toElement();
const QVariant checkConfiguration = QgsXmlUtils::readVariant( checkConfigurationElem );
mCheckConfiguration = checkConfiguration.toMap();
} }

View File

@ -22,6 +22,8 @@
#include "qgis_sip.h" #include "qgis_sip.h"
#include "qgsgeometry.h" #include "qgsgeometry.h"
#include <QObject>
/** /**
* \ingroup core * \ingroup core
* *
@ -30,8 +32,10 @@
* *
* \since QGIS 3.4 * \since QGIS 3.4
*/ */
class CORE_EXPORT QgsGeometryOptions class CORE_EXPORT QgsGeometryOptions : public QObject
{ {
Q_OBJECT
public: public:
/** /**
@ -85,6 +89,34 @@ class CORE_EXPORT QgsGeometryOptions
*/ */
void apply( QgsGeometry &geometry ) const; void apply( QgsGeometry &geometry ) const;
/**
* A list of activated geometry checks.
*
* \since QGIS 3.4
*/
QStringList geometryChecks() const;
/**
* A list of activated geometry checks.
*
* \since QGIS 3.4
*/
void setGeometryChecks( const QStringList &geometryChecks );
/**
* Access the configuration for the check \a checkId.
*
* \since QGIS 3.4
*/
QVariantMap checkConfiguration( const QString &checkId ) const;
/**
* Set the configuration for the check \a checkId.
*
* \since QGIS 3.4
*/
void setCheckConfiguration( const QString &checkId, const QVariantMap &checkConfiguration );
/** /**
* Write the geometry options to the \a node. * Write the geometry options to the \a node.
* *
@ -99,6 +131,38 @@ class CORE_EXPORT QgsGeometryOptions
*/ */
void readXml( const QDomNode &node ); void readXml( const QDomNode &node );
signals:
/**
* Access the configuration for the check \a checkId.
*
* \since QGIS 3.4
*/
void checkConfigurationChanged();
/**
* A list of activated geometry checks.
*
* \since QGIS 3.4
*/
void geometryChecksChanged();
/**
* Automatically remove duplicate nodes on all geometries which are edited on this layer.
*
* \since QGIS 3.4
*/
void removeDuplicateNodesChanged();
/**
* The precision in which geometries on this layer should be saved.
* Geometries which are edited on this layer will be rounded to multiples of this value (snap to grid).
* Set to 0.0 to disable.
*
* \since QGIS 3.4
*/
void geometryPrecisionChanged();
private: private:
/** /**
@ -116,6 +180,9 @@ class CORE_EXPORT QgsGeometryOptions
* \since QGIS 3.4 * \since QGIS 3.4
*/ */
double mGeometryPrecision = 0.0; double mGeometryPrecision = 0.0;
QStringList mGeometryChecks;
QVariantMap mCheckConfiguration;
}; };
#endif // QGSGEOMETRYOPTIONS_H #endif // QGSGEOMETRYOPTIONS_H

View File

@ -81,16 +81,6 @@ void QgsMapToolCapture::deactivate()
QgsMapToolAdvancedDigitizing::deactivate(); QgsMapToolAdvancedDigitizing::deactivate();
} }
void QgsMapToolCapture::validationFinished()
{
emit messageDiscarded();
QString msgFinished = tr( "Validation finished" );
if ( !mValidationWarnings.isEmpty() )
{
emit messageEmitted( mValidationWarnings.join( QStringLiteral( "\n" ) ).append( "\n" ).append( msgFinished ), Qgis::Warning );
}
}
void QgsMapToolCapture::currentLayerChanged( QgsMapLayer *layer ) void QgsMapToolCapture::currentLayerChanged( QgsMapLayer *layer )
{ {
if ( !mCaptureModeFromLayer ) if ( !mCaptureModeFromLayer )
@ -688,7 +678,6 @@ void QgsMapToolCapture::validateGeometry()
mValidator = nullptr; mValidator = nullptr;
} }
mValidationWarnings.clear();
mGeomErrors.clear(); mGeomErrors.clear();
while ( !mGeomErrorMarkers.isEmpty() ) while ( !mGeomErrorMarkers.isEmpty() )
{ {
@ -726,20 +715,17 @@ void QgsMapToolCapture::validateGeometry()
method = QgsGeometry::ValidatorGeos; method = QgsGeometry::ValidatorGeos;
mValidator = new QgsGeometryValidator( geom, nullptr, method ); mValidator = new QgsGeometryValidator( geom, nullptr, method );
connect( mValidator, &QgsGeometryValidator::errorFound, this, &QgsMapToolCapture::addError ); connect( mValidator, &QgsGeometryValidator::errorFound, this, &QgsMapToolCapture::addError );
connect( mValidator, &QThread::finished, this, &QgsMapToolCapture::validationFinished );
mValidator->start(); mValidator->start();
QgsDebugMsgLevel( QStringLiteral( "Validation started" ), 4 ); QgsDebugMsgLevel( QStringLiteral( "Validation started" ), 4 );
} }
void QgsMapToolCapture::addError( QgsGeometry::Error e ) void QgsMapToolCapture::addError( const QgsGeometry::Error &e )
{ {
mGeomErrors << e; mGeomErrors << e;
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mCanvas->currentLayer() ); QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mCanvas->currentLayer() );
if ( !vlayer ) if ( !vlayer )
return; return;
mValidationWarnings << e.what();
if ( e.hasWhere() ) if ( e.hasWhere() )
{ {
QgsVertexMarker *vm = new QgsVertexMarker( mCanvas ); QgsVertexMarker *vm = new QgsVertexMarker( mCanvas );
@ -751,9 +737,6 @@ void QgsMapToolCapture::addError( QgsGeometry::Error e )
vm->setZValue( vm->zValue() + 1 ); vm->setZValue( vm->zValue() + 1 );
mGeomErrorMarkers << vm; mGeomErrorMarkers << vm;
} }
emit messageDiscarded();
emit messageEmitted( mValidationWarnings.join( QStringLiteral( "\n" ) ), Qgis::Warning );
} }
int QgsMapToolCapture::size() int QgsMapToolCapture::size()

View File

@ -107,11 +107,9 @@ class GUI_EXPORT QgsMapToolCapture : public QgsMapToolAdvancedDigitizing
void clean() override; void clean() override;
private slots: private slots:
void validationFinished(); void addError( const QgsGeometry::Error &error );
void addError( QgsGeometry::Error );
void currentLayerChanged( QgsMapLayer *layer ); void currentLayerChanged( QgsMapLayer *layer );
protected: protected:
/** /**
@ -264,7 +262,6 @@ class GUI_EXPORT QgsMapToolCapture : public QgsMapToolAdvancedDigitizing
QList<QgsPointLocator::Match> mSnappingMatches; QList<QgsPointLocator::Match> mSnappingMatches;
void validateGeometry(); void validateGeometry();
QStringList mValidationWarnings;
QgsGeometryValidator *mValidator = nullptr; QgsGeometryValidator *mValidator = nullptr;
QList< QgsGeometry::Error > mGeomErrors; QList< QgsGeometry::Error > mGeomErrors;
QList< QgsVertexMarker * > mGeomErrorMarkers; QList< QgsVertexMarker * > mGeomErrorMarkers;

View File

@ -51,6 +51,9 @@ void QgsRubberBand::setColor( const QColor &color )
void QgsRubberBand::setFillColor( const QColor &color ) void QgsRubberBand::setFillColor( const QColor &color )
{ {
if ( mBrush.color() == color )
return;
mBrush.setColor( color ); mBrush.setColor( color );
} }

View File

@ -81,7 +81,7 @@ void QgsGeometryCheckerFixSummaryDialog::addError( QTableWidget *table, QgsGeome
table->insertRow( row ); table->insertRow( row );
table->setItem( row, 0, new QTableWidgetItem( !error->layerId().isEmpty() ? mChecker->featurePools()[error->layerId()]->layer()->name() : "" ) ); table->setItem( row, 0, new QTableWidgetItem( !error->layerId().isEmpty() ? mChecker->featurePools()[error->layerId()]->layer()->name() : "" ) );
QTableWidgetItem *idItem = new QTableWidgetItem(); QTableWidgetItem *idItem = new QTableWidgetItem();
idItem->setData( Qt::EditRole, error->featureId() != FEATUREID_NULL ? QVariant( error->featureId() ) : QVariant() ); idItem->setData( Qt::EditRole, error->featureId() != FID_NULL ? QVariant( error->featureId() ) : QVariant() );
table->setItem( row, 1, idItem ); table->setItem( row, 1, idItem );
table->setItem( row, 2, new QTableWidgetItem( error->description() ) ); table->setItem( row, 2, new QTableWidgetItem( error->description() ) );
table->setItem( row, 3, new QTableWidgetItem( posStr ) ); table->setItem( row, 3, new QTableWidgetItem( posStr ) );

View File

@ -151,7 +151,7 @@ void QgsGeometryCheckerResultTab::addError( QgsGeometryCheckError *error )
ui.tableWidgetErrors->insertRow( row ); ui.tableWidgetErrors->insertRow( row );
QTableWidgetItem *idItem = new QTableWidgetItem(); QTableWidgetItem *idItem = new QTableWidgetItem();
idItem->setData( Qt::EditRole, error->featureId() != FEATUREID_NULL ? QVariant( error->featureId() ) : QVariant() ); idItem->setData( Qt::EditRole, error->featureId() != FID_NULL ? QVariant( error->featureId() ) : QVariant() );
ui.tableWidgetErrors->setItem( row, 0, new QTableWidgetItem( !error->layerId().isEmpty() ? mChecker->featurePools()[error->layerId()]->layer()->name() : "" ) ); ui.tableWidgetErrors->setItem( row, 0, new QTableWidgetItem( !error->layerId().isEmpty() ? mChecker->featurePools()[error->layerId()]->layer()->name() : "" ) );
ui.tableWidgetErrors->setItem( row, 1, idItem ); ui.tableWidgetErrors->setItem( row, 1, idItem );
ui.tableWidgetErrors->setItem( row, 2, new QTableWidgetItem( error->description() ) ); ui.tableWidgetErrors->setItem( row, 2, new QTableWidgetItem( error->description() ) );

View File

@ -0,0 +1,204 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QgsGeometryValidationDockBase</class>
<widget class="QgsDockWidget" name="QgsGeometryValidationDockBase">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>418</width>
<height>404</height>
</rect>
</property>
<property name="windowTitle">
<string>Geometry Validation</string>
</property>
<widget class="QWidget" name="dockWidgetContents">
<layout class="QGridLayout" name="gridLayout_2">
<item row="3" column="0" colspan="2">
<widget class="QWidget" name="mProblemDetailWidget" native="true">
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="1" column="6">
<widget class="QToolButton" name="mNextButton">
<property name="text">
<string>Next</string>
</property>
<property name="arrowType">
<enum>Qt::RightArrow</enum>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QToolButton" name="mPreviousButton">
<property name="text">
<string>Previous</string>
</property>
<property name="arrowType">
<enum>Qt::LeftArrow</enum>
</property>
</widget>
</item>
<item row="1" column="2">
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="3">
<widget class="QToolButton" name="mZoomToFeatureButton">
<property name="text">
<string>Zoom To Feature</string>
</property>
<property name="icon">
<iconset resource="../../images/images.qrc">
<normaloff>:/images/themes/default/mActionZoomToSelected.svg</normaloff>:/images/themes/default/mActionZoomToSelected.svg</iconset>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
</widget>
</item>
<item row="1" column="4">
<widget class="QToolButton" name="mZoomToProblemButton">
<property name="text">
<string>Zoom To Problem</string>
</property>
<property name="icon">
<iconset resource="../../images/images.qrc">
<normaloff>:/images/themes/default/mActionZoomToLayer.svg</normaloff>:/images/themes/default/mActionZoomToLayer.svg</iconset>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
</widget>
</item>
<item row="1" column="7">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="5">
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="2" colspan="4">
<widget class="QLabel" name="mProblemDescriptionLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Detailed Description</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="2" colspan="4">
<widget class="QWidget" name="mResolutionWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QListView" name="mErrorListView"/>
</item>
<item row="0" column="1">
<widget class="QToolButton" name="mTopologyChecksPendingButton">
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../images/images.qrc">
<normaloff>:/images/themes/default/mIconTopologicalEditing.svg</normaloff>:/images/themes/default/mIconTopologicalEditing.svg</iconset>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="mValidationRunningLabel">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>QgsDockWidget</class>
<extends>QDockWidget</extends>
<header>qgsdockwidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources>
<include location="../../images/images.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -23,8 +23,8 @@
<iconset resource="../../images/images.qrc"> <iconset resource="../../images/images.qrc">
<normaloff>:/images/icons/qgis-icon-16x16.png</normaloff>:/images/icons/qgis-icon-16x16.png</iconset> <normaloff>:/images/icons/qgis-icon-16x16.png</normaloff>:/images/icons/qgis-icon-16x16.png</iconset>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QVBoxLayout" name="verticalLayout_26">
<item row="0" column="0"> <item>
<widget class="QSplitter" name="mOptionsSplitter"> <widget class="QSplitter" name="mOptionsSplitter">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
@ -357,7 +357,7 @@
</sizepolicy> </sizepolicy>
</property> </property>
<property name="currentIndex"> <property name="currentIndex">
<number>1</number> <number>18</number>
</property> </property>
<widget class="QWidget" name="mOptsPage_Information"> <widget class="QWidget" name="mOptsPage_Information">
<layout class="QVBoxLayout" name="verticalLayout_5"> <layout class="QVBoxLayout" name="verticalLayout_5">
@ -433,7 +433,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>289</width> <width>325</width>
<height>389</height> <height>389</height>
</rect> </rect>
</property> </property>
@ -2358,56 +2358,86 @@ border-radius: 2px;</string>
<number>0</number> <number>0</number>
</property> </property>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QGroupBox" name="mGeometryAutoFixesGroupBox"> <widget class="QScrollArea" name="scrollArea_2">
<property name="title"> <property name="widgetResizable">
<string>Automatic Fixes</string> <bool>true</bool>
</property> </property>
<layout class="QFormLayout" name="formLayout"> <widget class="QWidget" name="scrollAreaWidgetContents_2">
<item row="1" column="0"> <property name="geometry">
<widget class="QLabel" name="label"> <rect>
<property name="toolTip"> <x>0</x>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The geometry precision defines the maximum precision to of geometry coordinates that should be stored on this layer. A snap to grid algorithm will be applied on every geometry entering this layer, resulting in coordinates being rounded to multiples of this value. The operation is applied in this layer's coordinate reference system.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <y>0</y>
</property> <width>651</width>
<property name="text"> <height>804</height>
<string>Geometry precision</string> </rect>
</property> </property>
</widget> <layout class="QVBoxLayout" name="verticalLayout_20">
</item> <item>
<item row="0" column="0" colspan="2"> <widget class="QGroupBox" name="mGeometryAutoFixesGroupBox">
<widget class="QCheckBox" name="mRemoveDuplicateNodesCheckbox"> <property name="title">
<property name="text"> <string>Automatic Fixes</string>
<string>Remove duplicate nodes</string> </property>
</property> <layout class="QFormLayout" name="formLayout">
</widget> <item row="2" column="0">
</item> <widget class="QLabel" name="label">
<item row="1" column="1"> <property name="toolTip">
<layout class="QHBoxLayout" name="horizontalLayout_11" stretch="1,2"> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The geometry precision defines the maximum precision to of geometry coordinates that should be stored on this layer. A snap to grid algorithm will be applied on every geometry entering this layer, resulting in coordinates being rounded to multiples of this value. The operation is applied in this layer's coordinate reference system.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<item> </property>
<widget class="QgsDoubleSpinBox" name="mGeometryPrecisionSpinBox"> <property name="text">
<property name="toolTip"> <string>Geometry precision</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The geometry precision defines the maximum precision to of geometry coordinates that should be stored on this layer. A snap to grid algorithm will be applied on every geometry entering this layer, resulting in coordinates being rounded to multiples of this value. The operation is applied in this layer's coordinate reference system.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> </property>
</property> </widget>
<property name="specialValueText"> </item>
<string>[Disabled]</string> <item row="0" column="0" colspan="2">
</property> <widget class="QCheckBox" name="mRemoveDuplicateNodesCheckbox">
</widget> <property name="text">
</item> <string>Remove duplicate nodes</string>
<item> </property>
<spacer name="horizontalSpacer_9"> </widget>
<property name="orientation"> </item>
<enum>Qt::Horizontal</enum> <item row="2" column="1">
</property> <layout class="QHBoxLayout" name="horizontalLayout_11" stretch="0,0">
<property name="sizeHint" stdset="0"> <item>
<size> <widget class="QLabel" name="mPrecisionUnitsLabel">
<width>40</width> <property name="text">
<height>20</height> <string>[Units]</string>
</size> </property>
</property> </widget>
</spacer> </item>
</item> <item>
</layout> <widget class="QLineEdit" name="mGeometryPrecisionLineEdit">
</item> <property name="inputMethodHints">
</layout> <set>Qt::ImhFormattedNumbersOnly</set>
</property>
<property name="text">
<string/>
</property>
<property name="placeholderText">
<string>[No precision restriction]</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="mGeometryValidationGroupBox">
<property name="title">
<string>Geometry checks</string>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="mTopologyChecksGroupBox">
<property name="title">
<string>Topology checks</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget> </widget>
</item> </item>
</layout> </layout>
@ -2418,7 +2448,7 @@ border-radius: 2px;</string>
</widget> </widget>
</widget> </widget>
</item> </item>
<item row="1" column="0"> <item>
<widget class="QFrame" name="mButtonBoxFrame"> <widget class="QFrame" name="mButtonBoxFrame">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum"> <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
@ -2461,18 +2491,18 @@ border-radius: 2px;</string>
</layout> </layout>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget>
<class>QgsScrollArea</class>
<extends>QScrollArea</extends>
<header>qgsscrollarea.h</header>
<container>1</container>
</customwidget>
<customwidget> <customwidget>
<class>QgsCollapsibleGroupBox</class> <class>QgsCollapsibleGroupBox</class>
<extends>QGroupBox</extends> <extends>QGroupBox</extends>
<header>qgscollapsiblegroupbox.h</header> <header>qgscollapsiblegroupbox.h</header>
<container>1</container> <container>1</container>
</customwidget> </customwidget>
<customwidget>
<class>QgsScrollArea</class>
<extends>QScrollArea</extends>
<header>qgsscrollarea.h</header>
<container>1</container>
</customwidget>
<customwidget> <customwidget>
<class>QgsProjectionSelectionWidget</class> <class>QgsProjectionSelectionWidget</class>
<extends>QWidget</extends> <extends>QWidget</extends>
@ -2529,11 +2559,6 @@ border-radius: 2px;</string>
<header>qgsvectorlayerlegendwidget.h</header> <header>qgsvectorlayerlegendwidget.h</header>
<container>1</container> <container>1</container>
</customwidget> </customwidget>
<customwidget>
<class>QgsDoubleSpinBox</class>
<extends>QDoubleSpinBox</extends>
<header>qgsdoublespinbox.h</header>
</customwidget>
</customwidgets> </customwidgets>
<tabstops> <tabstops>
<tabstop>mSearchLineEdit</tabstop> <tabstop>mSearchLineEdit</tabstop>
@ -2596,7 +2621,6 @@ border-radius: 2px;</string>
<tabstop>mLayerLegendUrlLineEdit</tabstop> <tabstop>mLayerLegendUrlLineEdit</tabstop>
<tabstop>mLayerLegendUrlFormatComboBox</tabstop> <tabstop>mLayerLegendUrlFormatComboBox</tabstop>
<tabstop>mRemoveDuplicateNodesCheckbox</tabstop> <tabstop>mRemoveDuplicateNodesCheckbox</tabstop>
<tabstop>mGeometryPrecisionSpinBox</tabstop>
</tabstops> </tabstops>
<resources> <resources>
<include location="../../images/images.qrc"/> <include location="../../images/images.qrc"/>

View File

@ -33,25 +33,29 @@ INCLUDE_DIRECTORIES(SYSTEM
#No relinking and full RPATH for the install tree #No relinking and full RPATH for the install tree
#See: http://www.cmake.org/Wiki/CMake_RPATH_handling#No_relinking_and_full_RPATH_for_the_install_tree #See: http://www.cmake.org/Wiki/CMake_RPATH_handling#No_relinking_and_full_RPATH_for_the_install_tree
MACRO (ADD_QGIS_TEST testname testsrc) MACRO (ADD_QGIS_TEST TESTSRC)
SET(qgis_${testname}_SRCS ${testsrc} ${util_SRCS}) SET (TESTNAME ${TESTSRC})
ADD_CUSTOM_TARGET(qgis_${testname}moc ALL DEPENDS ${qgis_${testname}_MOC_SRCS}) STRING(REPLACE "test" "" TESTNAME ${TESTNAME})
ADD_EXECUTABLE(qgis_${testname} ${qgis_${testname}_SRCS}) STRING(REPLACE "qgs" "" TESTNAME ${TESTNAME})
SET_TARGET_PROPERTIES(qgis_${testname} PROPERTIES AUTOMOC TRUE) STRING(REPLACE ".cpp" "" TESTNAME ${TESTNAME})
TARGET_LINK_LIBRARIES(qgis_${testname} SET (TESTNAME "qgis_${TESTNAME}test")
${Qt5Xml_LIBRARIES}
SET(${TESTNAME}_SRCS ${TESTSRC} ${util_SRCS})
SET(${TESTNAME}_MOC_CPPS ${TESTSRC})
ADD_EXECUTABLE(${TESTNAME} ${${TESTNAME}_SRCS})
SET_TARGET_PROPERTIES(${TESTNAME} PROPERTIES AUTOMOC TRUE)
TARGET_LINK_LIBRARIES(${TESTNAME}
${Qt5Core_LIBRARIES} ${Qt5Core_LIBRARIES}
${Qt5Svg_LIBRARIES}
${Qt5Test_LIBRARIES} ${Qt5Test_LIBRARIES}
${PROJ_LIBRARY}
${GEOS_LIBRARY}
${GDAL_LIBRARY}
qgis_core
qgis_analysis) qgis_analysis)
ADD_TEST(qgis_${testname} ${CMAKE_CURRENT_BINARY_DIR}/../../../output/bin/qgis_${testname} -maxwarnings 10000) ADD_TEST(${TESTNAME} ${CMAKE_BINARY_DIR}/output/bin/${TESTNAME} -maxwarnings 10000)
#SET_TARGET_PROPERTIES(qgis_${testname} PROPERTIES
# INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/${QGIS_LIB_DIR}
# INSTALL_RPATH_USE_LINK_PATH true )
ENDMACRO (ADD_QGIS_TEST) ENDMACRO (ADD_QGIS_TEST)
############################################################# #############################################################
# Tests: # Tests:
ADD_QGIS_TEST( qgsgeometrycheckstest testqgsgeometrychecks.cpp ) ADD_QGIS_TEST(testqgsgeometrychecks.cpp)
ADD_QGIS_TEST(testqgsvectorlayerfeaturepool.cpp)

View File

@ -41,6 +41,7 @@
#include "qgsgeometrysliverpolygoncheck.h" #include "qgsgeometrysliverpolygoncheck.h"
#include "qgsvectordataproviderfeaturepool.h" #include "qgsvectordataproviderfeaturepool.h"
#include "qgsproject.h" #include "qgsproject.h"
#include "qgsfeedback.h"
#include "qgsgeometrytypecheck.h" #include "qgsgeometrytypecheck.h"
@ -88,6 +89,7 @@ class TestQgsGeometryChecks: public QObject
void testLineLayerIntersectionCheck(); void testLineLayerIntersectionCheck();
void testMultipartCheck(); void testMultipartCheck();
void testOverlapCheck(); void testOverlapCheck();
void testOverlapCheckNoMaxArea();
void testPointCoveredByLineCheck(); void testPointCoveredByLineCheck();
void testPointInPolygonCheck(); void testPointInPolygonCheck();
void testSegmentLengthCheck(); void testSegmentLengthCheck();
@ -125,7 +127,8 @@ void TestQgsGeometryChecks::testAngleCheck()
configuration.insert( "minAngle", 15 ); configuration.insert( "minAngle", 15 );
QgsGeometryAngleCheck check( testContext.first, configuration ); QgsGeometryAngleCheck check( testContext.first, configuration );
check.collectErrors( testContext.second, checkErrors, messages ); QgsFeedback feedback;
check.collectErrors( testContext.second, checkErrors, messages, &feedback );
listErrors( checkErrors, messages ); listErrors( checkErrors, messages );
QList<QgsGeometryCheckError *> errs1; QList<QgsGeometryCheckError *> errs1;
@ -203,7 +206,8 @@ void TestQgsGeometryChecks::testAreaCheck()
configuration.insert( "areaThreshold", 0.04 ); configuration.insert( "areaThreshold", 0.04 );
QgsGeometryAreaCheck check( testContext.first, configuration ); QgsGeometryAreaCheck check( testContext.first, configuration );
check.collectErrors( testContext.second, checkErrors, messages ); QgsFeedback feedback;
check.collectErrors( testContext.second, checkErrors, messages, &feedback );
listErrors( checkErrors, messages ); listErrors( checkErrors, messages );
QList<QgsGeometryCheckError *> errs1; QList<QgsGeometryCheckError *> errs1;
@ -295,7 +299,8 @@ void TestQgsGeometryChecks::testContainedCheck()
QStringList messages; QStringList messages;
QgsGeometryContainedCheck check( testContext.first, QVariantMap() ); QgsGeometryContainedCheck check( testContext.first, QVariantMap() );
check.collectErrors( testContext.second, checkErrors, messages ); QgsFeedback feedback;
check.collectErrors( testContext.second, checkErrors, messages, &feedback );
listErrors( checkErrors, messages ); listErrors( checkErrors, messages );
QList<QgsGeometryCheckError *> errs1; QList<QgsGeometryCheckError *> errs1;
@ -332,7 +337,8 @@ void TestQgsGeometryChecks::testDangleCheck()
QStringList messages; QStringList messages;
QgsGeometryDangleCheck check( testContext.first, QVariantMap() ); QgsGeometryDangleCheck check( testContext.first, QVariantMap() );
check.collectErrors( testContext.second, checkErrors, messages ); QgsFeedback feedback;
check.collectErrors( testContext.second, checkErrors, messages, &feedback );
listErrors( checkErrors, messages ); listErrors( checkErrors, messages );
QList<QgsGeometryCheckError *> errs1; QList<QgsGeometryCheckError *> errs1;
@ -369,7 +375,8 @@ void TestQgsGeometryChecks::testDegeneratePolygonCheck()
QStringList messages; QStringList messages;
QgsGeometryDegeneratePolygonCheck check( testContext.first, QVariantMap() ); QgsGeometryDegeneratePolygonCheck check( testContext.first, QVariantMap() );
check.collectErrors( testContext.second, checkErrors, messages ); QgsFeedback feedback;
check.collectErrors( testContext.second, checkErrors, messages, &feedback );
listErrors( checkErrors, messages ); listErrors( checkErrors, messages );
QList<QgsGeometryCheckError *> errs1; QList<QgsGeometryCheckError *> errs1;
@ -404,7 +411,8 @@ void TestQgsGeometryChecks::testDuplicateCheck()
QStringList messages; QStringList messages;
QgsGeometryDuplicateCheck check( testContext.first, QVariantMap() ); QgsGeometryDuplicateCheck check( testContext.first, QVariantMap() );
check.collectErrors( testContext.second, checkErrors, messages ); QgsFeedback feedback;
check.collectErrors( testContext.second, checkErrors, messages, &feedback );
listErrors( checkErrors, messages ); listErrors( checkErrors, messages );
QList<QgsGeometryCheckError *> errs1; QList<QgsGeometryCheckError *> errs1;
@ -448,7 +456,8 @@ void TestQgsGeometryChecks::testDuplicateNodesCheck()
QStringList messages; QStringList messages;
QgsGeometryDuplicateNodesCheck check( testContext.first, QVariantMap() ); QgsGeometryDuplicateNodesCheck check( testContext.first, QVariantMap() );
check.collectErrors( testContext.second, checkErrors, messages ); QgsFeedback feedback;
check.collectErrors( testContext.second, checkErrors, messages, &feedback );
listErrors( checkErrors, messages ); listErrors( checkErrors, messages );
QList<QgsGeometryCheckError *> errs1; QList<QgsGeometryCheckError *> errs1;
@ -487,7 +496,8 @@ void TestQgsGeometryChecks::testFollowBoundariesCheck()
QList<QgsGeometryCheckError *> checkErrors; QList<QgsGeometryCheckError *> checkErrors;
QStringList messages; QStringList messages;
QgsGeometryFollowBoundariesCheck( testContext.first, QVariantMap(), testContext.second[layers["follow_ref.shp"]]->layer() ).collectErrors( testContext.second, checkErrors, messages ); QgsFeedback feedback;
QgsGeometryFollowBoundariesCheck( testContext.first, QVariantMap(), testContext.second[layers["follow_ref.shp"]]->layer() ).collectErrors( testContext.second, checkErrors, messages, &feedback );
listErrors( checkErrors, messages ); listErrors( checkErrors, messages );
QCOMPARE( checkErrors.size(), 2 ); QCOMPARE( checkErrors.size(), 2 );
@ -512,7 +522,8 @@ void TestQgsGeometryChecks::testGapCheck()
configuration.insert( "gapThreshold", 0.01 ); configuration.insert( "gapThreshold", 0.01 );
QgsGeometryGapCheck check( testContext.first, configuration ); QgsGeometryGapCheck check( testContext.first, configuration );
check.collectErrors( testContext.second, checkErrors, messages ); QgsFeedback feedback;
check.collectErrors( testContext.second, checkErrors, messages, &feedback );
listErrors( checkErrors, messages ); listErrors( checkErrors, messages );
QList<QgsGeometryCheckError *> errs1; QList<QgsGeometryCheckError *> errs1;
@ -553,7 +564,8 @@ void TestQgsGeometryChecks::testMissingVertexCheck()
QStringList messages; QStringList messages;
QgsGeometryMissingVertexCheck check( testContext.first, QVariantMap() ); QgsGeometryMissingVertexCheck check( testContext.first, QVariantMap() );
check.collectErrors( testContext.second, checkErrors, messages ); QgsFeedback feedback;
check.collectErrors( testContext.second, checkErrors, messages, &feedback );
listErrors( checkErrors, messages ); listErrors( checkErrors, messages );
const QString layerId = testContext.second.first()->layerId(); const QString layerId = testContext.second.first()->layerId();
@ -582,7 +594,8 @@ void TestQgsGeometryChecks::testHoleCheck()
QStringList messages; QStringList messages;
QgsGeometryHoleCheck check( testContext.first, QVariantMap() ); QgsGeometryHoleCheck check( testContext.first, QVariantMap() );
check.collectErrors( testContext.second, checkErrors, messages ); QgsFeedback feedback;
check.collectErrors( testContext.second, checkErrors, messages, &feedback );
listErrors( checkErrors, messages ); listErrors( checkErrors, messages );
QList<QgsGeometryCheckError *> errs1; QList<QgsGeometryCheckError *> errs1;
@ -620,7 +633,8 @@ void TestQgsGeometryChecks::testLineIntersectionCheck()
QStringList messages; QStringList messages;
QgsGeometryLineIntersectionCheck check( testContext.first, QVariantMap() ); QgsGeometryLineIntersectionCheck check( testContext.first, QVariantMap() );
check.collectErrors( testContext.second, checkErrors, messages ); QgsFeedback feedback;
check.collectErrors( testContext.second, checkErrors, messages, &feedback );
listErrors( checkErrors, messages ); listErrors( checkErrors, messages );
QCOMPARE( checkErrors.size(), 1 ); QCOMPARE( checkErrors.size(), 1 );
@ -648,7 +662,8 @@ void TestQgsGeometryChecks::testLineLayerIntersectionCheck()
configuration.insert( "checkLayer", layers["polygon_layer.shp"] ); configuration.insert( "checkLayer", layers["polygon_layer.shp"] );
QgsGeometryLineLayerIntersectionCheck check( testContext.first, configuration ); QgsGeometryLineLayerIntersectionCheck check( testContext.first, configuration );
check.collectErrors( testContext.second, checkErrors, messages ); QgsFeedback feedback;
check.collectErrors( testContext.second, checkErrors, messages, &feedback );
listErrors( checkErrors, messages ); listErrors( checkErrors, messages );
QCOMPARE( checkErrors.size(), 5 ); QCOMPARE( checkErrors.size(), 5 );
@ -677,7 +692,8 @@ void TestQgsGeometryChecks::testMultipartCheck()
QStringList messages; QStringList messages;
QgsGeometryMultipartCheck check( testContext.first, QVariantMap() ); QgsGeometryMultipartCheck check( testContext.first, QVariantMap() );
check.collectErrors( testContext.second, checkErrors, messages ); QgsFeedback feedback;
check.collectErrors( testContext.second, checkErrors, messages, &feedback );
listErrors( checkErrors, messages ); listErrors( checkErrors, messages );
QVERIFY( searchCheckErrors( checkErrors, layers["point_layer.shp"] ).isEmpty() ); QVERIFY( searchCheckErrors( checkErrors, layers["point_layer.shp"] ).isEmpty() );
@ -728,6 +744,7 @@ void TestQgsGeometryChecks::testOverlapCheck()
layers.insert( "point_layer.shp", "" ); layers.insert( "point_layer.shp", "" );
layers.insert( "line_layer.shp", "" ); layers.insert( "line_layer.shp", "" );
layers.insert( "polygon_layer.shp", "" ); layers.insert( "polygon_layer.shp", "" );
auto testContext = createTestContext( dir, layers ); auto testContext = createTestContext( dir, layers );
// Test detection // Test detection
@ -738,7 +755,8 @@ void TestQgsGeometryChecks::testOverlapCheck()
configuration.insert( "maxOverlapArea", 0.01 ); configuration.insert( "maxOverlapArea", 0.01 );
QgsGeometryOverlapCheck check( testContext.first, configuration ); QgsGeometryOverlapCheck check( testContext.first, configuration );
check.collectErrors( testContext.second, checkErrors, messages ); QgsFeedback feedback;
check.collectErrors( testContext.second, checkErrors, messages, &feedback );
listErrors( checkErrors, messages ); listErrors( checkErrors, messages );
QList<QgsGeometryCheckError *> errs1; QList<QgsGeometryCheckError *> errs1;
@ -765,6 +783,38 @@ void TestQgsGeometryChecks::testOverlapCheck()
cleanupTestContext( testContext ); cleanupTestContext( testContext );
} }
void TestQgsGeometryChecks::testOverlapCheckNoMaxArea()
{
QTemporaryDir dir;
QMap<QString, QString> layers;
layers.insert( QStringLiteral( "point_layer.shp" ), QString() );
layers.insert( QStringLiteral( "line_layer.shp" ), QString() );
layers.insert( QStringLiteral( "polygon_layer.shp" ), QString() );
auto testContext = createTestContext( dir, layers );
// Test detection
QList<QgsGeometryCheckError *> checkErrors;
QStringList messages;
QVariantMap configuration;
configuration.insert( "maxOverlapArea", 0.0 );
QgsGeometryOverlapCheck check( testContext.first, configuration );
QgsFeedback feedback;
check.collectErrors( testContext.second, checkErrors, messages, &feedback );
listErrors( checkErrors, messages );
QList<QgsGeometryCheckError *> errs1;
QCOMPARE( checkErrors.size(), 4 );
QVERIFY( searchCheckErrors( checkErrors, layers["point_layer.shp"] ).isEmpty() );
QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"] ).isEmpty() );
errs1 = searchCheckErrors( checkErrors, layers["polygon_layer.shp"], 10 );
QCOMPARE( errs1.size(), 2 );
errs1 = searchCheckErrors( checkErrors, layers["polygon_layer.shp"], 9 );
QCOMPARE( errs1.size(), 2 );
}
void TestQgsGeometryChecks::testPointCoveredByLineCheck() void TestQgsGeometryChecks::testPointCoveredByLineCheck()
{ {
QTemporaryDir dir; QTemporaryDir dir;
@ -779,7 +829,8 @@ void TestQgsGeometryChecks::testPointCoveredByLineCheck()
QStringList messages; QStringList messages;
QgsGeometryPointCoveredByLineCheck errs( testContext.first, QVariantMap() ); QgsGeometryPointCoveredByLineCheck errs( testContext.first, QVariantMap() );
errs.collectErrors( testContext.second, checkErrors, messages ); QgsFeedback feedback;
errs.collectErrors( testContext.second, checkErrors, messages, &feedback );
listErrors( checkErrors, messages ); listErrors( checkErrors, messages );
QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"] ).isEmpty() ); QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"] ).isEmpty() );
@ -805,7 +856,8 @@ void TestQgsGeometryChecks::testPointInPolygonCheck()
QStringList messages; QStringList messages;
QgsGeometryPointInPolygonCheck check( testContext.first, QVariantMap() ); QgsGeometryPointInPolygonCheck check( testContext.first, QVariantMap() );
check.collectErrors( testContext.second, checkErrors, messages ); QgsFeedback feedback;
check.collectErrors( testContext.second, checkErrors, messages, &feedback );
listErrors( checkErrors, messages ); listErrors( checkErrors, messages );
QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"] ).isEmpty() ); QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"] ).isEmpty() );
@ -834,7 +886,8 @@ void TestQgsGeometryChecks::testSegmentLengthCheck()
configuration.insert( "minSegmentLength", 0.03 ); configuration.insert( "minSegmentLength", 0.03 );
QgsGeometrySegmentLengthCheck check( testContext.first, configuration ); QgsGeometrySegmentLengthCheck check( testContext.first, configuration );
check.collectErrors( testContext.second, checkErrors, messages ); QgsFeedback feedback;
check.collectErrors( testContext.second, checkErrors, messages, &feedback );
listErrors( checkErrors, messages ); listErrors( checkErrors, messages );
QCOMPARE( checkErrors.size(), 4 ); QCOMPARE( checkErrors.size(), 4 );
@ -861,7 +914,8 @@ void TestQgsGeometryChecks::testSelfContactCheck()
QStringList messages; QStringList messages;
QgsGeometrySelfContactCheck check( testContext.first, QVariantMap() ); QgsGeometrySelfContactCheck check( testContext.first, QVariantMap() );
check.collectErrors( testContext.second, checkErrors, messages ); QgsFeedback feedback;
check.collectErrors( testContext.second, checkErrors, messages, &feedback );
listErrors( checkErrors, messages ); listErrors( checkErrors, messages );
QCOMPARE( checkErrors.size(), 3 ); QCOMPARE( checkErrors.size(), 3 );
@ -887,7 +941,8 @@ void TestQgsGeometryChecks::testSelfIntersectionCheck()
QStringList messages; QStringList messages;
QgsGeometrySelfIntersectionCheck check( testContext.first, QVariantMap() ); QgsGeometrySelfIntersectionCheck check( testContext.first, QVariantMap() );
check.collectErrors( testContext.second, checkErrors, messages ); QgsFeedback feedback;
check.collectErrors( testContext.second, checkErrors, messages, &feedback );
listErrors( checkErrors, messages ); listErrors( checkErrors, messages );
QList<QgsGeometryCheckError *> errs1; QList<QgsGeometryCheckError *> errs1;
@ -994,7 +1049,8 @@ void TestQgsGeometryChecks::testSliverPolygonCheck()
configuration.insert( "threshold", 20 ); configuration.insert( "threshold", 20 );
configuration.insert( "maxArea", 0.04 ); configuration.insert( "maxArea", 0.04 );
QgsGeometrySliverPolygonCheck( testContext.first, configuration ).collectErrors( testContext.second, checkErrors, messages ); QgsFeedback feedback;
QgsGeometrySliverPolygonCheck( testContext.first, configuration ).collectErrors( testContext.second, checkErrors, messages, &feedback );
listErrors( checkErrors, messages ); listErrors( checkErrors, messages );
QCOMPARE( checkErrors.size(), 2 ); QCOMPARE( checkErrors.size(), 2 );

View File

@ -0,0 +1,171 @@
/***************************************************************************
testqgsgeometrychecks.cpp
--------------------------------------
Date : September 2017
Copyright : (C) 2017 Sandro Mani
Email : manisandro at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgstest.h"
#include "qgsvectorlayerfeaturepool.h"
#include "qgsvectorlayer.h"
class TestQgsVectorLayerFeaturePool : public QObject
{
Q_OBJECT
private slots:
void initTestCase();
void cleanupTestCase();
void getFeatures();
void addFeature();
void deleteFeature();
void changeGeometry();
private:
std::unique_ptr<QgsVectorLayer> createPopulatedLayer();
};
void TestQgsVectorLayerFeaturePool::initTestCase()
{
QgsApplication::initQgis();
}
void TestQgsVectorLayerFeaturePool::cleanupTestCase()
{
QgsApplication::exitQgis();
}
void TestQgsVectorLayerFeaturePool::getFeatures()
{
std::unique_ptr<QgsVectorLayer> vl = createPopulatedLayer();
QgsVectorLayerFeaturePool pool( vl.get() );
QgsFeatureIds ids1 = pool.getFeatures( QgsFeatureRequest().setFilterRect( QgsRectangle( 0, 0, 10, 10 ) ) );
// One feature is within the requested area
QCOMPARE( ids1.size(), 1 );
QgsFeatureIds ids2 = pool.getIntersects( QgsRectangle( 0, 0, 10, 10 ) );
// Also one within the spatial index
QCOMPARE( ids2.size(), 1 );
}
void TestQgsVectorLayerFeaturePool::addFeature()
{
std::unique_ptr<QgsVectorLayer> vl = createPopulatedLayer();
QgsVectorLayerFeaturePool pool( vl.get() );
QgsFeatureIds ids1 = pool.getFeatures( QgsFeatureRequest().setFilterRect( QgsRectangle( 0, 0, 10, 10 ) ) );
// One feature is within the requested area
QCOMPARE( ids1.size(), 1 );
QgsFeatureIds ids2 = pool.getIntersects( QgsRectangle( 0, 0, 10, 10 ) );
// Also one within the spatial index
QCOMPARE( ids2.size(), 1 );
QgsFeature feature;
feature.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "Polygon((1 1, 9 1, 9 9, 1 9, 1 1))" ) ) );
// Add another feature...
vl->startEditing();
pool.addFeature( feature );
QgsFeatureIds ids3 = pool.getIntersects( QgsRectangle( 0, 0, 10, 10 ) );
// Two features within the requested area
QCOMPARE( ids3.size(), 2 );
}
void TestQgsVectorLayerFeaturePool::deleteFeature()
{
std::unique_ptr<QgsVectorLayer> vl = createPopulatedLayer();
QgsVectorLayerFeaturePool pool( vl.get() );
QgsFeatureIds ids1 = pool.getFeatures( QgsFeatureRequest().setFilterRect( QgsRectangle( 0, 0, 10, 10 ) ) );
// One feature is within the requested area
QCOMPARE( ids1.size(), 1 );
QgsFeatureIds ids2 = pool.getIntersects( QgsRectangle( 0, 0, 10, 10 ) );
// Also one within the spatial index
QCOMPARE( ids2.size(), 1 );
vl->startEditing();
// Delete a feature outside the AOI (0, 0, 10, 10)
pool.deleteFeature( 2 );
QgsFeatureIds ids3 = pool.getIntersects( QgsRectangle( 0, 0, 10, 10 ) );
// Still 1 feature
QCOMPARE( ids3.size(), 1 );
// Delete a feature inside the AOI (0, 0, 10, 10)
pool.deleteFeature( 1 );
QgsFeatureIds ids4 = pool.getIntersects( QgsRectangle( 0, 0, 10, 10 ) );
// No more features here
QCOMPARE( ids4.size(), 0 );
}
void TestQgsVectorLayerFeaturePool::changeGeometry()
{
std::unique_ptr<QgsVectorLayer> vl = createPopulatedLayer();
QgsVectorLayerFeaturePool pool( vl.get() );
// One feature is within the requested area
QgsFeatureIds ids1 = pool.getFeatures( QgsFeatureRequest().setFilterRect( QgsRectangle( 0, 0, 10, 10 ) ) );
QCOMPARE( ids1.size(), 1 );
// Also one when using the spatial index
QgsFeatureIds ids2 = pool.getIntersects( QgsRectangle( 0, 0, 10, 10 ) );
QCOMPARE( ids2.size(), 1 );
vl->startEditing();
QgsFeature feat;
pool.getFeature( 1, feat );
// Update a feature to be outside the AOI
feat.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "Polygon((100 100, 110 100, 110 110, 100 110, 100 100))" ) ) );
vl->updateFeature( feat );
// No features in the AOI
QgsFeatureIds ids3 = pool.getIntersects( QgsRectangle( 0, 0, 10, 10 ) );
QCOMPARE( ids3.size(), 0 );
// Update a feature to be inside the AOI
feat.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "Polygon((0 0, 10 0, 10 10, 0 10, 0 0))" ) ) );
vl->updateFeature( feat );
// One there again
QgsFeatureIds ids4 = pool.getIntersects( QgsRectangle( 0, 0, 10, 10 ) );
QCOMPARE( ids4.size(), 1 );
}
std::unique_ptr<QgsVectorLayer> TestQgsVectorLayerFeaturePool::createPopulatedLayer()
{
std::unique_ptr<QgsVectorLayer> vl = qgis::make_unique<QgsVectorLayer>( QStringLiteral( "Polygon" ), QStringLiteral( "Polygons" ), QStringLiteral( "memory" ) );
QgsFeature feature;
feature.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "Polygon((0 0, 10 0, 10 10, 0 10, 0 0))" ) ) );
vl->dataProvider()->addFeature( feature );
feature.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "Polygon((100 100, 110 100, 110 110, 100 110, 100 100))" ) ) );
vl->dataProvider()->addFeature( feature );
return vl;
}
QGSTEST_MAIN( TestQgsVectorLayerFeaturePool )
#include "testqgsvectorlayerfeaturepool.moc"