QGIS API Documentation 4.3.0-Master (ffcfc20b9b4)
Loading...
Searching...
No Matches
qgsalgorithmcheckvalidity.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsalgorithmcheckvalidity.cpp
3 ---------------------
4 begin : February 2025
5 copyright : (C) 2025 by Alexander Bruy
6 email : alexander dot bruy at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
19
21#include "qgsvectorlayer.h"
22
23#include <QString>
24
25using namespace Qt::StringLiterals;
26
28
29QString QgsCheckValidityAlgorithm::name() const
30{
31 return u"checkvalidity"_s;
32}
33
34QString QgsCheckValidityAlgorithm::displayName() const
35{
36 return QObject::tr( "Check validity" );
37}
38
39QStringList QgsCheckValidityAlgorithm::tags() const
40{
41 return QObject::tr( "valid,invalid,detect,error" ).split( ',' );
42}
43
44QString QgsCheckValidityAlgorithm::group() const
45{
46 return QObject::tr( "Vector geometry" );
47}
48
49QString QgsCheckValidityAlgorithm::groupId() const
50{
51 return u"vectorgeometry"_s;
52}
53
54QString QgsCheckValidityAlgorithm::shortHelpString() const
55{
56 return QObject::tr(
57 "This algorithm performs a validity check on the geometries of a vector layer.\n\n"
58 "The geometries are classified in three groups (valid, invalid and error), and a vector layer "
59 "is generated with the features in each of these categories.\n\n"
60 "By default the algorithm uses the strict OGC definition of polygon validity, where a polygon "
61 "is marked as invalid if a self-intersecting ring causes an interior hole. If the 'Ignore "
62 "ring self intersections' option is checked, then this rule will be ignored and a more "
63 "lenient validity check will be performed.\n\n"
64 "The GEOS method is faster and performs better on larger geometries, but is limited to only "
65 "returning the first error encountered in a geometry. The QGIS method will be slower but "
66 "reports all errors encountered in the geometry, not just the first. The SFCGAL method "
67 "performs 3D validity checks, and should be used when validating three-dimensional geometries."
68 );
69}
70
71QString QgsCheckValidityAlgorithm::shortDescription() const
72{
73 return QObject::tr(
74 "Performs a validity check on the geometries of a vector layer "
75 "and classifies them in three groups (valid, invalid and error)."
76 );
77}
78
79QgsCheckValidityAlgorithm *QgsCheckValidityAlgorithm::createInstance() const
80{
81 return new QgsCheckValidityAlgorithm();
82}
83
84void QgsCheckValidityAlgorithm::initAlgorithm( const QVariantMap & )
85{
86 addParameter( new QgsProcessingParameterFeatureSource( u"INPUT_LAYER"_s, QObject::tr( "Input layer" ), QList<int>() << static_cast<int>( Qgis::ProcessingSourceType::VectorAnyGeometry ) ) );
87
88 const QStringList options = QStringList() << QObject::tr( "The one selected in digitizing settings" ) << u"QGIS"_s << u"GEOS"_s << u"SFCGAL"_s;
89 auto methodParam = std::make_unique<QgsProcessingParameterEnum>( u"METHOD"_s, QObject::tr( "Method" ), options, false, 2 );
90 QVariantMap methodParamMetadata;
91 QVariantMap widgetMetadata;
92 widgetMetadata.insert( u"useCheckBoxes"_s, true );
93 widgetMetadata.insert( u"columns"_s, 3 );
94 methodParamMetadata.insert( u"widget_wrapper"_s, widgetMetadata );
95 methodParam->setMetadata( methodParamMetadata );
96 addParameter( methodParam.release() );
97
98 addParameter( new QgsProcessingParameterBoolean( u"IGNORE_RING_SELF_INTERSECTION"_s, QObject::tr( "Ignore ring self intersections" ), false ) );
99 addParameter( new QgsProcessingParameterFeatureSink( u"VALID_OUTPUT"_s, QObject::tr( "Valid output" ), Qgis::ProcessingSourceType::VectorAnyGeometry, QVariant(), true ) );
100 addParameter( new QgsProcessingParameterFeatureSink( u"INVALID_OUTPUT"_s, QObject::tr( "Invalid output" ), Qgis::ProcessingSourceType::VectorAnyGeometry, QVariant(), true ) );
101 addParameter( new QgsProcessingParameterFeatureSink( u"ERROR_OUTPUT"_s, QObject::tr( "Error output" ), Qgis::ProcessingSourceType::VectorAnyGeometry, QVariant(), true ) );
102 addOutput( new QgsProcessingOutputNumber( u"VALID_COUNT"_s, QObject::tr( "Count of valid features" ) ) );
103 addOutput( new QgsProcessingOutputNumber( u"INVALID_COUNT"_s, QObject::tr( "Count of invalid features" ) ) );
104 addOutput( new QgsProcessingOutputNumber( u"ERROR_COUNT"_s, QObject::tr( "Count of errors" ) ) );
105}
106
107bool QgsCheckValidityAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
108{
109 mValidationMethod = parameterAsEnum( parameters, u"METHOD"_s, context );
110 if ( mValidationMethod == 0 )
111 {
112 const int methodFromSettings = QgsSettingsRegistryCore::settingsDigitizingValidateGeometries->value() - 1;
113 mValidationMethod = methodFromSettings > 0 ? methodFromSettings : 0;
114 }
115 else
116 {
117 mValidationMethod--;
118 }
119
120 // SFCGAL method selected but SFCGAL support is disabled
121 if ( mValidationMethod == 2 && !Qgis::hasSfcgal() )
122 {
123 throw QgsProcessingException( QObject::tr( "SFCGAL validation method requires a QGIS installation with SFCGAL support enabled. Please use a version of QGIS that includes SFCGAL." ) );
124 }
125
126 return true;
127}
128
129QVariantMap QgsCheckValidityAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
130{
131 QGS_MARK_ALGORITHM_SOURCE
132
133 std::unique_ptr<QgsProcessingFeatureSource> source( parameterAsSource( parameters, u"INPUT_LAYER"_s, context ) );
134 if ( !source )
135 throw QgsProcessingException( invalidSourceError( parameters, u"INPUT_LAYER"_s ) );
136
137 const bool ignoreRingSelfIntersection = parameterAsBool( parameters, u"IGNORE_RING_SELF_INTERSECTION"_s, context );
138
140
141 QString validSinkId;
142 std::unique_ptr<QgsFeatureSink> validSink( parameterAsSink( parameters, u"VALID_OUTPUT"_s, context, validSinkId, source->fields(), source->wkbType(), source->sourceCrs() ) );
143
144 QgsFields invalidFields = source->fields();
145 invalidFields.append( QgsField( u"_errors"_s, QMetaType::Type::QString, u"string"_s, 255 ) );
146 QString invalidSinkId;
147 std::unique_ptr<QgsFeatureSink> invalidSink( parameterAsSink( parameters, u"INVALID_OUTPUT"_s, context, invalidSinkId, invalidFields, source->wkbType(), source->sourceCrs() ) );
148
149 QgsFields errorFields;
150 errorFields.append( QgsField( u"message"_s, QMetaType::Type::QString, u"string"_s, 255 ) );
151 QString errorSinkId;
152 std::unique_ptr<QgsFeatureSink> errorSink( parameterAsSink( parameters, u"ERROR_OUTPUT"_s, context, errorSinkId, errorFields, Qgis::WkbType::Point, source->sourceCrs() ) );
153
154 int validCount = 0;
155 int invalidCount = 0;
156 int errorCount = 0;
157
158 const long count = source->featureCount();
159 const double step = count > 0 ? 100.0 / count : 1;
160 long long current = 0;
161
163 QgsFeature f;
164 while ( it.nextFeature( f ) )
165 {
166 if ( feedback->isCanceled() )
167 {
168 break;
169 }
170
171 const QgsGeometry geom = f.geometry();
172 QgsAttributes attrs = f.attributes();
173
174 bool isValid = true;
175
176 if ( !geom.isNull() && !geom.isEmpty() )
177 {
178 QVector< QgsGeometry::Error > errors;
179 geom.validateGeometry( errors, Qgis::GeometryValidationEngine( mValidationMethod ), flags );
180 if ( errors.count() > 0 )
181 {
182 isValid = false;
183 QStringList reasons;
184 reasons.reserve( errors.count() );
185 for ( const QgsGeometry::Error &error : std::as_const( errors ) )
186 {
187 if ( errorSink )
188 {
189 QgsFeature f;
190 f.setGeometry( QgsGeometry::fromPointXY( error.where() ) );
191 f.setAttributes( QVector< QVariant >() << error.what() );
192 if ( !errorSink->addFeature( f, QgsFeatureSink::FastInsert ) )
193 {
194 throw QgsProcessingException( writeFeatureError( errorSink.get(), parameters, u"ERROR_OUTPUT"_s ) );
195 }
196 else
197 {
198 feedback->featureAddedToSink( u"ERROR_OUTPUT"_s );
199 }
200 }
201 errorCount++;
202 reasons.append( error.what() );
203 }
204 QString reason = reasons.join( '\n' );
205 if ( reason.size() > 255 )
206 {
207 reason = reason.left( 252 ) + u"…"_s;
208 }
209 attrs.append( reason );
210 }
211 }
212
213 QgsFeature f;
214 f.setGeometry( geom );
215 f.setAttributes( attrs );
216
217 if ( isValid )
218 {
219 if ( validSink && !validSink->addFeature( f, QgsFeatureSink::FastInsert ) )
220 {
221 throw QgsProcessingException( writeFeatureError( validSink.get(), parameters, u"VALID_OUTPUT"_s ) );
222 }
223 else if ( validSink )
224 {
225 feedback->featureAddedToSink( u"VALID_OUTPUT"_s );
226 }
227 validCount++;
228 }
229 else
230 {
231 if ( invalidSink && !invalidSink->addFeature( f, QgsFeatureSink::FastInsert ) )
232 {
233 throw QgsProcessingException( writeFeatureError( invalidSink.get(), parameters, u"INVALID_OUTPUT"_s ) );
234 }
235 else if ( invalidSink )
236 {
237 feedback->featureAddedToSink( u"INVALID_OUTPUT"_s );
238 }
239 invalidCount++;
240 }
241
242 feedback->setProgress( current * step );
243 current++;
244 }
245
246 if ( validSink )
247 {
248 validSink->finalize();
249 feedback->featureSinkFinalized( u"VALID_OUTPUT"_s );
250 }
251 if ( invalidSink )
252 {
253 invalidSink->finalize();
254 feedback->featureSinkFinalized( u"INVALID_OUTPUT"_s );
255 }
256 if ( errorSink )
257 {
258 errorSink->finalize();
259 feedback->featureSinkFinalized( u"ERROR_OUTPUT"_s );
260 }
261
262 QVariantMap outputs;
263 outputs.insert( u"VALID_COUNT"_s, validCount );
264 outputs.insert( u"INVALID_COUNT"_s, invalidCount );
265 outputs.insert( u"ERROR_COUNT"_s, errorCount );
266
267 if ( validSink )
268 {
269 outputs.insert( u"VALID_OUTPUT"_s, validSinkId );
270 }
271 if ( invalidSink )
272 {
273 outputs.insert( u"INVALID_OUTPUT"_s, invalidSinkId );
274 }
275 if ( errorSink )
276 {
277 outputs.insert( u"ERROR_OUTPUT"_s, errorSinkId );
278 }
279
280 return outputs;
281}
282
@ VectorAnyGeometry
Any vector layer with geometry.
Definition qgis.h:3749
@ AllowSelfTouchingHoles
Indicates that self-touching holes are permitted. OGC validity states that self-touching holes are NO...
Definition qgis.h:2222
QFlags< GeometryValidityFlag > GeometryValidityFlags
Geometry validity flags.
Definition qgis.h:2226
GeometryValidationEngine
Available engines for validating geometries.
Definition qgis.h:2235
static bool hasSfcgal()
Returns true if the QGIS build contains SFCGAL.
Definition qgis.cpp:709
@ SkipGeometryValidityChecks
Invalid geometry checks should always be skipped. This flag can be useful for algorithms which always...
Definition qgis.h:3930
@ Point
Point.
Definition qgis.h:296
A vector of attributes.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
Wraps a request for features to a vector layer (or directly its vector data provider).
@ FastInsert
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:60
QgsAttributes attributes
Definition qgsfeature.h:64
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
QgsGeometry geometry
Definition qgsfeature.h:66
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:56
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:65
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:56
Container of fields for a vector layer.
Definition qgsfields.h:45
bool append(const QgsField &field, Qgis::FieldOrigin origin=Qgis::FieldOrigin::Provider, int originIndex=-1)
Appends a field.
Definition qgsfields.cpp:75
A geometry error.
A geometry is the spatial representation of a feature.
static QgsGeometry fromPointXY(const QgsPointXY &point)
Creates a new geometry from a QgsPointXY object.
void validateGeometry(QVector< QgsGeometry::Error > &errors, Qgis::GeometryValidationEngine method=Qgis::GeometryValidationEngine::QgisInternal, Qgis::GeometryValidityFlags flags=Qgis::GeometryValidityFlags()) const
Validates geometry and produces a list of geometry errors.
bool isEmpty() const
Returns true if the geometry is empty (eg a linestring with no vertices, or a collection with no geom...
Contains information about the context in which a processing algorithm is executed.
Custom exception class for processing related exceptions.
Base class for providing feedback from a processing algorithm.
void featureAddedToSink(const QString &output)
Reports that a feature was added to the the sink associated with the specified algorithm output.
void featureSinkFinalized(const QString &output)
Reports that a feature sink has been finalized.
A numeric output for processing algorithms.
A boolean parameter for processing algorithms.
A feature sink output for processing algorithms.
An input feature source (such as vector layers) parameter for processing algorithms.
static const QgsSettingsEntryInteger * settingsDigitizingValidateGeometries
Settings entry digitizing validate geometries.