QGIS API Documentation 3.99.0-Master (d270888f95f)
Loading...
Searching...
No Matches
qgsgeometryoverlapcheck.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsgeometryoverlapcheck.cpp
3 ---------------------
4 begin : September 2015
5 copyright : (C) 2014 by Sandro Mani / Sourcepole AG
6 email : smani at sourcepole dot ch
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
17
18#include "qgsapplication.h"
19#include "qgsfeaturepool.h"
20#include "qgsfeedback.h"
22#include "qgsgeometryengine.h"
23#include "qgsvectorlayer.h"
24
25#include <QString>
26
27using namespace Qt::StringLiterals;
28
30 : QgsGeometryCheck( context, configuration )
31 , mOverlapThresholdMapUnits( configurationValue<double>( u"maxOverlapArea"_s ) )
32
33{
34}
35
36QgsGeometryCheck::Result QgsGeometryOverlapCheck::collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids ) const
37{
38 QMap<QString, QSet<QVariant>> uniqueIds;
39 const QMap<QString, QgsFeatureIds> featureIds = ids.isEmpty() ? allLayerFeatureIds( featurePools ) : ids.toMap();
40 const QgsGeometryCheckerUtils::LayerFeatures layerFeaturesA( featurePools, featureIds, compatibleGeometryTypes(), feedback, mContext, true );
41 QList<QString> layerIds = featureIds.keys();
42 for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeatureA : layerFeaturesA )
43 {
44 if ( feedback && feedback->isCanceled() )
46
47
48 if ( context()->uniqueIdFieldIndex != -1 )
49 {
50 QgsGeometryCheck::Result result = checkUniqueId( layerFeatureA, uniqueIds );
52 {
53 return result;
54 }
55 }
56
57 // Ensure each pair of layers only gets compared once: remove the current layer from the layerIds, but add it to the layerList for layerFeaturesB
58 layerIds.removeOne( layerFeatureA.layer()->id() );
59
60 const QgsGeometry geomA = layerFeatureA.geometry();
61 const QgsRectangle bboxA = geomA.boundingBox();
62 std::unique_ptr<QgsGeometryEngine> geomEngineA( QgsGeometry::createGeometryEngine( geomA.constGet(), mContext->tolerance ) );
63 geomEngineA->prepareGeometry();
64 if ( !geomEngineA->isValid() )
65 {
66 messages.append( tr( "Overlap check failed for (%1): the geometry is invalid" ).arg( layerFeatureA.id() ) );
67 continue;
68 }
69
70 const QgsGeometryCheckerUtils::LayerFeatures layerFeaturesB( featurePools, QList<QString>() << layerFeatureA.layer()->id() << layerIds, bboxA, compatibleGeometryTypes(), mContext );
71 for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeatureB : layerFeaturesB )
72 {
73 if ( feedback && feedback->isCanceled() )
75
76 // only report overlaps within same layer once
77 if ( layerFeatureA.layerId() == layerFeatureB.layerId() && layerFeatureB.feature().id() >= layerFeatureA.feature().id() )
78 {
79 continue;
80 }
81
82 QString errMsg;
83 const QgsGeometry geometryB = layerFeatureB.geometry();
84 const QgsAbstractGeometry *geomB = geometryB.constGet();
85 if ( geomEngineA->overlaps( geomB, &errMsg ) )
86 {
87 std::unique_ptr<QgsAbstractGeometry> interGeom( geomEngineA->intersection( geomB ) );
88 if ( interGeom && !interGeom->isEmpty() )
89 {
91 for ( int iPart = 0, nParts = interGeom->partCount(); iPart < nParts; ++iPart )
92 {
93 QgsAbstractGeometry *interPart = QgsGeometryCheckerUtils::getGeomPart( interGeom.get(), iPart );
94 const double area = interPart->area();
95 if ( area > mContext->reducedTolerance && ( area < mOverlapThresholdMapUnits || mOverlapThresholdMapUnits == 0.0 ) )
96 {
97 errors.append( new QgsGeometryOverlapCheckError( this, layerFeatureA, QgsGeometry( interPart->clone() ), interPart->centroid(), area, layerFeatureB ) );
98 }
99 }
100 }
101 else if ( !errMsg.isEmpty() )
102 {
103 messages.append( tr( "Overlap check between features %1 and %2 %3" ).arg( layerFeatureA.id(), layerFeatureB.id(), errMsg ) );
104 }
105 }
106 }
107 }
109}
110
111void QgsGeometryOverlapCheck::fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> & /*mergeAttributeIndices*/, Changes &changes ) const
112{
113 QString errMsg;
114 QgsGeometryOverlapCheckError *overlapError = static_cast<QgsGeometryOverlapCheckError *>( error );
115
116 QgsFeaturePool *featurePoolA = featurePools[overlapError->layerId()];
117 QgsFeaturePool *featurePoolB = featurePools[overlapError->overlappedFeature().layerId()];
118 QgsFeature featureA;
119 QgsFeature featureB;
120 if ( !featurePoolA->getFeature( overlapError->featureId(), featureA ) || !featurePoolB->getFeature( overlapError->overlappedFeature().featureId(), featureB ) )
121 {
122 error->setObsolete();
123 return;
124 }
125
126 // Check if error still applies
127 const QgsGeometryCheckerUtils::LayerFeature layerFeatureA( featurePoolA, featureA, mContext, true );
128 const QgsGeometryCheckerUtils::LayerFeature layerFeatureB( featurePoolB, featureB, mContext, true );
129 const QgsGeometry geometryA = layerFeatureA.geometry();
130 std::unique_ptr<QgsGeometryEngine> geomEngineA( QgsGeometry::createGeometryEngine( geometryA.constGet(), mContext->tolerance ) );
131 geomEngineA->prepareGeometry();
132
133 const QgsGeometry geometryB = layerFeatureB.geometry();
134 if ( !geomEngineA->overlaps( geometryB.constGet() ) )
135 {
136 error->setObsolete();
137 return;
138 }
139 std::unique_ptr<QgsAbstractGeometry> interGeom( geomEngineA->intersection( geometryB.constGet(), &errMsg ) );
140 if ( !interGeom )
141 {
142 error->setFixFailed( tr( "Failed to compute intersection between overlapping features: %1" ).arg( errMsg ) );
143 return;
144 }
145
146 // Search which overlap part this error parametrizes (using fuzzy-matching of the area and centroid...)
147 QgsAbstractGeometry *interPart = nullptr;
148 for ( int iPart = 0, nParts = interGeom->partCount(); iPart < nParts; ++iPart )
149 {
150 QgsAbstractGeometry *part = QgsGeometryCheckerUtils::getGeomPart( interGeom.get(), iPart );
151 if ( std::fabs( part->area() - overlapError->value().toDouble() ) < mContext->reducedTolerance && QgsGeometryUtilsBase::fuzzyDistanceEqual( mContext->reducedTolerance, part->centroid().x(), part->centroid().y(), overlapError->location().x(), overlapError->location().y() ) ) // TODO: add fuzzyDistanceEqual in QgsAbstractGeometry classes
152 {
153 interPart = part;
154 break;
155 }
156 }
157 if ( !interPart || interPart->isEmpty() )
158 {
159 error->setObsolete();
160 return;
161 }
162
163 // Fix error
164 if ( method == NoChange )
165 {
166 error->setFixed( method );
167 }
168 else if ( method == Subtract )
169 {
170 std::unique_ptr<QgsGeometryEngine> geomEngineDiffA( QgsGeometry::createGeometryEngine( geometryA.constGet(), 0 ) );
171 std::unique_ptr<QgsAbstractGeometry> diff1( geomEngineDiffA->difference( interPart, &errMsg ) );
172 if ( !diff1 || diff1->isEmpty() )
173 {
174 diff1.reset();
175 }
176 else
177 {
179 }
180 std::unique_ptr<QgsGeometryEngine> geomEngineDiffB( QgsGeometry::createGeometryEngine( geometryB.constGet(), 0 ) );
181 std::unique_ptr<QgsAbstractGeometry> diff2( geomEngineDiffB->difference( interPart, &errMsg ) );
182 if ( !diff2 || diff2->isEmpty() )
183 {
184 diff2.reset();
185 }
186 else
187 {
189 }
190 const double shared1 = diff1 ? QgsGeometryCheckerUtils::sharedEdgeLength( diff1.get(), interPart, mContext->reducedTolerance ) : 0;
191 const double shared2 = diff2 ? QgsGeometryCheckerUtils::sharedEdgeLength( diff2.get(), interPart, mContext->reducedTolerance ) : 0;
192 if ( !diff1 || !diff2 || shared1 == 0. || shared2 == 0. )
193 {
194 error->setFixFailed( tr( "Could not find shared edges between intersection and overlapping features" ) );
195 }
196 else
197 {
198 if ( shared1 < shared2 )
199 {
200 const QgsCoordinateTransform ct( featurePoolA->crs(), mContext->mapCrs, mContext->transformContext );
201 diff1->transform( ct, Qgis::TransformDirection::Reverse );
202 featureA.setGeometry( QgsGeometry( std::move( diff1 ) ) );
203
204 changes[error->layerId()][featureA.id()].append( Change( ChangeFeature, ChangeChanged ) );
205 featurePoolA->updateFeature( featureA );
206 }
207 else
208 {
209 const QgsCoordinateTransform ct( featurePoolB->crs(), mContext->mapCrs, mContext->transformContext );
210 diff2->transform( ct, Qgis::TransformDirection::Reverse );
211 featureB.setGeometry( QgsGeometry( std::move( diff2 ) ) );
212
213 changes[overlapError->overlappedFeature().layerId()][featureB.id()].append( Change( ChangeFeature, ChangeChanged ) );
214 featurePoolB->updateFeature( featureB );
215 }
216
217 error->setFixed( method );
218 }
219 }
220 else
221 {
222 error->setFixFailed( tr( "Unknown method" ) );
223 }
224}
225
227{
228 static const QStringList methods = QStringList()
229 << tr( "Remove overlapping area from neighboring polygon with shortest shared edge" )
230 << tr( "No action" );
231 return methods;
232}
233
235{
236 return factoryDescription();
237}
238
240{
241 return factoryId();
242}
243
245{
246 return factoryFlags();
247}
248
250QString QgsGeometryOverlapCheck::factoryDescription()
251{
252 return tr( "Overlap" );
253}
254
255QgsGeometryCheck::CheckType QgsGeometryOverlapCheck::factoryCheckType()
256{
258}
259
260QString QgsGeometryOverlapCheck::factoryId()
261{
262 return u"QgsGeometryOverlapCheck"_s;
263}
264
265QgsGeometryCheck::Flags QgsGeometryOverlapCheck::factoryFlags()
266{
268}
269
270QList<Qgis::GeometryType> QgsGeometryOverlapCheck::factoryCompatibleGeometryTypes()
271{
273}
274
275bool QgsGeometryOverlapCheck::factoryIsCompatible( QgsVectorLayer *layer ) SIP_SKIP
276{
277 return factoryCompatibleGeometryTypes().contains( layer->geometryType() );
278}
279
282 : QgsGeometryCheckError( check, layerFeature.layer()->id(), layerFeature.feature().id(), geometry, errorLocation, QgsVertexId(), value, ValueArea )
283 , mOverlappedFeature( OverlappedFeature( overlappedFeature.layer(), overlappedFeature.feature().id() ) )
284{
285}
286
288{
289 QgsGeometryOverlapCheckError *err = dynamic_cast<QgsGeometryOverlapCheckError *>( other );
290 return err && other->layerId() == layerId() && other->featureId() == featureId() && err->overlappedFeature() == overlappedFeature() && location().distanceCompare( other->location(), mCheck->context()->reducedTolerance ) && std::fabs( value().toDouble() - other->value().toDouble() ) < mCheck->context()->reducedTolerance;
291}
292
294{
295 QgsGeometryOverlapCheckError *err = dynamic_cast<QgsGeometryOverlapCheckError *>( other );
296 return err && other->layerId() == layerId() && other->featureId() == featureId() && err->overlappedFeature() == overlappedFeature();
297}
298
300{
301 if ( !QgsGeometryCheckError::handleChanges( changes ) )
302 {
303 return false;
304 }
305 if ( changes.value( mOverlappedFeature.layerId() ).contains( mOverlappedFeature.featureId() ) )
306 {
307 return false;
308 }
309 return true;
310}
311
313{
314 return QCoreApplication::translate( "QgsGeometryTypeCheckError", "Overlap with %1 at feature %2" ).arg( mOverlappedFeature.layerName(), QString::number( mOverlappedFeature.featureId() ) );
315}
316
317QMap<QString, QgsFeatureIds> QgsGeometryOverlapCheckError::involvedFeatures() const
318{
319 QMap<QString, QgsFeatureIds> features;
320 features[layerId()].insert( featureId() );
321 features[mOverlappedFeature.layerId()].insert( mOverlappedFeature.featureId() );
322 return features;
323}
324
326{
328 return QgsApplication::getThemeIcon( u"/algorithms/mAlgorithmCheckGeometry.svg"_s );
329 else
330 return QgsApplication::getThemeIcon( u"/checks/Overlap.svg"_s );
331}
@ Polygon
Polygons.
Definition qgis.h:368
@ Reverse
Reverse/inverse transform (from destination to source).
Definition qgis.h:2731
Abstract base class for all geometries.
virtual bool isEmpty() const
Returns true if the geometry is empty.
virtual QgsPoint centroid() const
Returns the centroid of the geometry.
virtual double area() const
Returns the planar, 2-dimensional area of the geometry.
virtual QgsAbstractGeometry * clone() const =0
Clones the geometry by performing a deep copy.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
Handles coordinate transforms between two coordinate systems.
A feature pool is based on a vector layer and caches features.
virtual void updateFeature(QgsFeature &feature)=0
Updates a feature in this pool.
QgsCoordinateReferenceSystem crs() const
The coordinate reference system of this layer.
bool getFeature(QgsFeatureId id, QgsFeature &feature)
Retrieves the feature with the specified id into feature.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:60
QgsFeatureId id
Definition qgsfeature.h:68
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition qgsfeedback.h:44
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:55
Base configuration for geometry checks.
This represents an error reported by a geometry check.
@ ValueArea
The value is an area.
@ StatusFixed
The error is fixed.
Status status() const
The status of the error.
QgsFeatureId featureId() const
The id of the feature on which this error has been detected.
const QgsGeometryCheck * mCheck
QgsGeometryCheckError(const QgsGeometryCheck *check, const QgsGeometryCheckerUtils::LayerFeature &layerFeature, const QgsPointXY &errorLocation, QgsVertexId vidx=QgsVertexId(), const QVariant &value=QVariant(), ValueType valueType=ValueOther)
Create a new geometry check error with the parent check and for the layerFeature pair at the errorLoc...
void setFixed(int method)
Set the status to fixed and specify the method that has been used to fix the error.
void setFixFailed(const QString &reason)
Set the error status to failed and specify the reason for failure.
QgsGeometry geometry() const
The geometry of the error in map units.
QVariant value() const
An additional value for the error.
void setObsolete()
Set the error status to obsolete.
const QgsGeometryCheck * check() const
The geometry check that created this error.
const QString & layerId() const
The id of the layer on which this error has been detected.
virtual bool handleChanges(const QgsGeometryCheck::Changes &changes)
Apply a list of changes.
const QgsPointXY & location() const
The location of the error in map units.
Base class for geometry checks.
QMap< QString, QMap< QgsFeatureId, QList< QgsGeometryCheck::Change > > > Changes
A collection of changes.
QFlags< Flag > Flags
T configurationValue(const QString &name, const QVariant &defaultValue=QVariant())
Returns the configuration value with the name, saved in the QGIS settings for this geometry check.
const QgsGeometryCheckContext * mContext
@ ChangeFeature
This change happens on feature level.
@ AvailableInValidation
This geometry check should be available in layer validation on the vector layer properties.
CheckType
The type of a check.
@ LayerCheck
The check controls a whole layer (topology checks).
QMap< QString, QgsFeatureIds > allLayerFeatureIds(const QMap< QString, QgsFeaturePool * > &featurePools) const
Returns all layers and feature ids.
Result checkUniqueId(const QgsGeometryCheckerUtils::LayerFeature layerFeature, QMap< QString, QSet< QVariant > > &uniqueIds) const
Checks that there are no duplicated unique IDs.
Result
Result of the geometry checker operation.
@ Canceled
User canceled calculation.
@ Success
Operation completed successfully.
@ ChangeChanged
Something has been updated.
QgsGeometryCheck(const QgsGeometryCheckContext *context, const QVariantMap &configuration)
Create a new geometry check.
const QgsGeometryCheckContext * context() const
Returns the context.
A layer feature combination to uniquely identify and access a feature in a set of layers.
QgsGeometry geometry() const
Returns the geometry of this feature.
Contains a set of layers and feature ids in those layers to pass to a geometry check.
static void filter1DTypes(QgsAbstractGeometry *geom)
static QgsAbstractGeometry * getGeomPart(QgsAbstractGeometry *geom, int partIdx)
static double sharedEdgeLength(const QgsAbstractGeometry *geom1, const QgsAbstractGeometry *geom2, double tol)
An error of a QgsGeometryOverlapCheck.
QMap< QString, QgsFeatureIds > involvedFeatures() const override
Returns a list of involved features.
QString description() const override
The error description.
QgsGeometryOverlapCheckError(const QgsGeometryCheck *check, const QgsGeometryCheckerUtils::LayerFeature &layerFeature, const QgsGeometry &geometry, const QgsPointXY &errorLocation, const QVariant &value, const QgsGeometryCheckerUtils::LayerFeature &overlappedFeature)
Creates a new overlap check error for check and the layerFeature combination.
const OverlappedFeature & overlappedFeature() const
Returns the overlapped feature.
QIcon icon() const override
Returns an icon that should be shown for this kind of error.
bool closeMatch(QgsGeometryCheckError *other) const override
Check if this error is almost equal to other.
bool isEqual(QgsGeometryCheckError *other) const override
Check if this error is equal to other.
bool handleChanges(const QgsGeometryCheck::Changes &changes) override
Apply a list of changes.
QgsGeometryCheck::Result collectErrors(const QMap< QString, QgsFeaturePool * > &featurePools, QList< QgsGeometryCheckError * > &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids=LayerFeatureIds()) const override
The main worker method.
void fixError(const QMap< QString, QgsFeaturePool * > &featurePools, QgsGeometryCheckError *error, int method, const QMap< QString, int > &mergeAttributeIndices, Changes &changes) const override
Fixes the error error with the specified method.
Q_DECL_DEPRECATED QStringList resolutionMethods() const override
Returns a list of descriptions for available resolutions for errors.
QgsGeometryCheck::Flags flags() const override
Flags for this geometry check.
@ Subtract
Subtract the overlap region from the polygon.
@ NoChange
Do not change anything.
QList< Qgis::GeometryType > compatibleGeometryTypes() const override
A list of geometry types for which this check can be performed.
QgsGeometryOverlapCheck(const QgsGeometryCheckContext *context, const QVariantMap &configuration)
Checks for overlapping polygons.
QString id() const override
Returns an id for this check.
QString description() const override
Returns a human readable description for this check.
static bool fuzzyDistanceEqual(T epsilon, const Args &... args) noexcept
Compare equality between multiple pairs of values with a specified epsilon.
A geometry is the spatial representation of a feature.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
static QgsGeometryEngine * createGeometryEngine(const QgsAbstractGeometry *geometry, double precision=0.0, Qgis::GeosCreationFlags flags=Qgis::GeosCreationFlag::SkipEmptyInteriorRings)
Creates and returns a new geometry engine representing the specified geometry using precision on a gr...
Represents a 2D point.
Definition qgspointxy.h:62
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
bool distanceCompare(const QgsPointXY &other, double epsilon=4 *std::numeric_limits< double >::epsilon()) const
Compares this point with another point with a fuzzy tolerance using distance comparison.
Definition qgspointxy.h:270
double x
Definition qgspoint.h:56
double y
Definition qgspoint.h:57
A rectangle specified with double values.
Represents a vector layer which manages a vector based dataset.
#define SIP_SKIP
Definition qgis_sip.h:134
Descripts a change to fix a geometry.
A list of layers and feature ids for each of these layers.
QMap< QString, QgsFeatureIds > toMap() const
Utility class for identifying a unique vertex within a geometry.
Definition qgsvertexid.h:34