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