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