QGIS API Documentation 3.99.0-Master (21b3aa880ba)
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
26 : QgsGeometryCheck( context, configuration )
27 , mOverlapThresholdMapUnits( configurationValue<double>( QStringLiteral( "maxOverlapArea" ) ) )
28
29{
30}
31
32QgsGeometryCheck::Result QgsGeometryOverlapCheck::collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids ) const
33{
34 QMap<QString, QSet<QVariant>> uniqueIds;
35 const QMap<QString, QgsFeatureIds> featureIds = ids.isEmpty() ? allLayerFeatureIds( featurePools ) : ids.toMap();
36 const QgsGeometryCheckerUtils::LayerFeatures layerFeaturesA( featurePools, featureIds, compatibleGeometryTypes(), feedback, mContext, true );
37 QList<QString> layerIds = featureIds.keys();
38 for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeatureA : layerFeaturesA )
39 {
40 if ( feedback && feedback->isCanceled() )
42
43
44 if ( context()->uniqueIdFieldIndex != -1 )
45 {
46 QgsGeometryCheck::Result result = checkUniqueId( layerFeatureA, uniqueIds );
48 {
49 return result;
50 }
51 }
52
53 // Ensure each pair of layers only gets compared once: remove the current layer from the layerIds, but add it to the layerList for layerFeaturesB
54 layerIds.removeOne( layerFeatureA.layer()->id() );
55
56 const QgsGeometry geomA = layerFeatureA.geometry();
57 const QgsRectangle bboxA = geomA.boundingBox();
58 std::unique_ptr<QgsGeometryEngine> geomEngineA( QgsGeometry::createGeometryEngine( geomA.constGet(), mContext->tolerance ) );
59 geomEngineA->prepareGeometry();
60 if ( !geomEngineA->isValid() )
61 {
62 messages.append( tr( "Overlap check failed for (%1): the geometry is invalid" ).arg( layerFeatureA.id() ) );
63 continue;
64 }
65
66 const QgsGeometryCheckerUtils::LayerFeatures layerFeaturesB( featurePools, QList<QString>() << layerFeatureA.layer()->id() << layerIds, bboxA, compatibleGeometryTypes(), mContext );
67 for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeatureB : layerFeaturesB )
68 {
69 if ( feedback && feedback->isCanceled() )
71
72 // only report overlaps within same layer once
73 if ( layerFeatureA.layerId() == layerFeatureB.layerId() && layerFeatureB.feature().id() >= layerFeatureA.feature().id() )
74 {
75 continue;
76 }
77
78 QString errMsg;
79 const QgsGeometry geometryB = layerFeatureB.geometry();
80 const QgsAbstractGeometry *geomB = geometryB.constGet();
81 if ( geomEngineA->overlaps( geomB, &errMsg ) )
82 {
83 std::unique_ptr<QgsAbstractGeometry> interGeom( geomEngineA->intersection( geomB ) );
84 if ( interGeom && !interGeom->isEmpty() )
85 {
87 for ( int iPart = 0, nParts = interGeom->partCount(); iPart < nParts; ++iPart )
88 {
89 QgsAbstractGeometry *interPart = QgsGeometryCheckerUtils::getGeomPart( interGeom.get(), iPart );
90 const double area = interPart->area();
91 if ( area > mContext->reducedTolerance && ( area < mOverlapThresholdMapUnits || mOverlapThresholdMapUnits == 0.0 ) )
92 {
93 errors.append( new QgsGeometryOverlapCheckError( this, layerFeatureA, QgsGeometry( interPart->clone() ), interPart->centroid(), area, layerFeatureB ) );
94 }
95 }
96 }
97 else if ( !errMsg.isEmpty() )
98 {
99 messages.append( tr( "Overlap check between features %1 and %2 %3" ).arg( layerFeatureA.id(), layerFeatureB.id(), errMsg ) );
100 }
101 }
102 }
103 }
105}
106
107void QgsGeometryOverlapCheck::fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> & /*mergeAttributeIndices*/, Changes &changes ) const
108{
109 QString errMsg;
110 QgsGeometryOverlapCheckError *overlapError = static_cast<QgsGeometryOverlapCheckError *>( error );
111
112 QgsFeaturePool *featurePoolA = featurePools[overlapError->layerId()];
113 QgsFeaturePool *featurePoolB = featurePools[overlapError->overlappedFeature().layerId()];
114 QgsFeature featureA;
115 QgsFeature featureB;
116 if ( !featurePoolA->getFeature( overlapError->featureId(), featureA ) || !featurePoolB->getFeature( overlapError->overlappedFeature().featureId(), featureB ) )
117 {
118 error->setObsolete();
119 return;
120 }
121
122 // Check if error still applies
123 const QgsGeometryCheckerUtils::LayerFeature layerFeatureA( featurePoolA, featureA, mContext, true );
124 const QgsGeometryCheckerUtils::LayerFeature layerFeatureB( featurePoolB, featureB, mContext, true );
125 const QgsGeometry geometryA = layerFeatureA.geometry();
126 std::unique_ptr<QgsGeometryEngine> geomEngineA( QgsGeometry::createGeometryEngine( geometryA.constGet(), mContext->tolerance ) );
127 geomEngineA->prepareGeometry();
128
129 const QgsGeometry geometryB = layerFeatureB.geometry();
130 if ( !geomEngineA->overlaps( geometryB.constGet() ) )
131 {
132 error->setObsolete();
133 return;
134 }
135 std::unique_ptr<QgsAbstractGeometry> interGeom( geomEngineA->intersection( geometryB.constGet(), &errMsg ) );
136 if ( !interGeom )
137 {
138 error->setFixFailed( tr( "Failed to compute intersection between overlapping features: %1" ).arg( errMsg ) );
139 return;
140 }
141
142 // Search which overlap part this error parametrizes (using fuzzy-matching of the area and centroid...)
143 QgsAbstractGeometry *interPart = nullptr;
144 for ( int iPart = 0, nParts = interGeom->partCount(); iPart < nParts; ++iPart )
145 {
146 QgsAbstractGeometry *part = QgsGeometryCheckerUtils::getGeomPart( interGeom.get(), iPart );
147 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
148 {
149 interPart = part;
150 break;
151 }
152 }
153 if ( !interPart || interPart->isEmpty() )
154 {
155 error->setObsolete();
156 return;
157 }
158
159 // Fix error
160 if ( method == NoChange )
161 {
162 error->setFixed( method );
163 }
164 else if ( method == Subtract )
165 {
166 std::unique_ptr<QgsGeometryEngine> geomEngineDiffA( QgsGeometry::createGeometryEngine( geometryA.constGet(), 0 ) );
167 std::unique_ptr<QgsAbstractGeometry> diff1( geomEngineDiffA->difference( interPart, &errMsg ) );
168 if ( !diff1 || diff1->isEmpty() )
169 {
170 diff1.reset();
171 }
172 else
173 {
175 }
176 std::unique_ptr<QgsGeometryEngine> geomEngineDiffB( QgsGeometry::createGeometryEngine( geometryB.constGet(), 0 ) );
177 std::unique_ptr<QgsAbstractGeometry> diff2( geomEngineDiffB->difference( interPart, &errMsg ) );
178 if ( !diff2 || diff2->isEmpty() )
179 {
180 diff2.reset();
181 }
182 else
183 {
185 }
186 const double shared1 = diff1 ? QgsGeometryCheckerUtils::sharedEdgeLength( diff1.get(), interPart, mContext->reducedTolerance ) : 0;
187 const double shared2 = diff2 ? QgsGeometryCheckerUtils::sharedEdgeLength( diff2.get(), interPart, mContext->reducedTolerance ) : 0;
188 if ( !diff1 || !diff2 || shared1 == 0. || shared2 == 0. )
189 {
190 error->setFixFailed( tr( "Could not find shared edges between intersection and overlapping features" ) );
191 }
192 else
193 {
194 if ( shared1 < shared2 )
195 {
196 const QgsCoordinateTransform ct( featurePoolA->crs(), mContext->mapCrs, mContext->transformContext );
197 diff1->transform( ct, Qgis::TransformDirection::Reverse );
198 featureA.setGeometry( QgsGeometry( std::move( diff1 ) ) );
199
200 changes[error->layerId()][featureA.id()].append( Change( ChangeFeature, ChangeChanged ) );
201 featurePoolA->updateFeature( featureA );
202 }
203 else
204 {
205 const QgsCoordinateTransform ct( featurePoolB->crs(), mContext->mapCrs, mContext->transformContext );
206 diff2->transform( ct, Qgis::TransformDirection::Reverse );
207 featureB.setGeometry( QgsGeometry( std::move( diff2 ) ) );
208
209 changes[overlapError->overlappedFeature().layerId()][featureB.id()].append( Change( ChangeFeature, ChangeChanged ) );
210 featurePoolB->updateFeature( featureB );
211 }
212
213 error->setFixed( method );
214 }
215 }
216 else
217 {
218 error->setFixFailed( tr( "Unknown method" ) );
219 }
220}
221
223{
224 static const QStringList methods = QStringList()
225 << tr( "Remove overlapping area from neighboring polygon with shortest shared edge" )
226 << tr( "No action" );
227 return methods;
228}
229
231{
232 return factoryDescription();
233}
234
236{
237 return factoryId();
238}
239
241{
242 return factoryFlags();
243}
244
246QString QgsGeometryOverlapCheck::factoryDescription()
247{
248 return tr( "Overlap" );
249}
250
251QgsGeometryCheck::CheckType QgsGeometryOverlapCheck::factoryCheckType()
252{
254}
255
256QString QgsGeometryOverlapCheck::factoryId()
257{
258 return QStringLiteral( "QgsGeometryOverlapCheck" );
259}
260
261QgsGeometryCheck::Flags QgsGeometryOverlapCheck::factoryFlags()
262{
264}
265
266QList<Qgis::GeometryType> QgsGeometryOverlapCheck::factoryCompatibleGeometryTypes()
267{
269}
270
271bool QgsGeometryOverlapCheck::factoryIsCompatible( QgsVectorLayer *layer ) SIP_SKIP
272{
273 return factoryCompatibleGeometryTypes().contains( layer->geometryType() );
274}
275
278 : QgsGeometryCheckError( check, layerFeature.layer()->id(), layerFeature.feature().id(), geometry, errorLocation, QgsVertexId(), value, ValueArea )
279 , mOverlappedFeature( OverlappedFeature( overlappedFeature.layer(), overlappedFeature.feature().id() ) )
280{
281}
282
284{
285 QgsGeometryOverlapCheckError *err = dynamic_cast<QgsGeometryOverlapCheckError *>( other );
286 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;
287}
288
290{
291 QgsGeometryOverlapCheckError *err = dynamic_cast<QgsGeometryOverlapCheckError *>( other );
292 return err && other->layerId() == layerId() && other->featureId() == featureId() && err->overlappedFeature() == overlappedFeature();
293}
294
296{
297 if ( !QgsGeometryCheckError::handleChanges( changes ) )
298 {
299 return false;
300 }
301 if ( changes.value( mOverlappedFeature.layerId() ).contains( mOverlappedFeature.featureId() ) )
302 {
303 return false;
304 }
305 return true;
306}
307
309{
310 return QCoreApplication::translate( "QgsGeometryTypeCheckError", "Overlap with %1 at feature %2" ).arg( mOverlappedFeature.layerName(), QString::number( mOverlappedFeature.featureId() ) );
311}
312
313QMap<QString, QgsFeatureIds> QgsGeometryOverlapCheckError::involvedFeatures() const
314{
315 QMap<QString, QgsFeatureIds> features;
316 features[layerId()].insert( featureId() );
317 features[mOverlappedFeature.layerId()].insert( mOverlappedFeature.featureId() );
318 return features;
319}
320
322{
324 return QgsApplication::getThemeIcon( QStringLiteral( "/algorithms/mAlgorithmCheckGeometry.svg" ) );
325 else
326 return QgsApplication::getThemeIcon( QStringLiteral( "/checks/Overlap.svg" ) );
327}
@ Polygon
Polygons.
Definition qgis.h:361
@ Reverse
Reverse/inverse transform (from destination to source).
Definition qgis.h:2673
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:58
QgsFeatureId id
Definition qgsfeature.h:66
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:53
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 peroperties.
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:60
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
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:268
double x
Definition qgspoint.h:52
double y
Definition qgspoint.h:53
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:30