Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgsgeometrymissingvertexcheck.cpp
3 : : ---------------------
4 : : begin : September 2018
5 : : copyright : (C) 2018 Matthias Kuhn
6 : : email : matthias@opengis.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 "qgsgeometrymissingvertexcheck.h"
17 : :
18 : : #include "qgsfeedback.h"
19 : : #include "qgsgeometrycollection.h"
20 : : #include "qgsmultipolygon.h"
21 : : #include "qgscurvepolygon.h"
22 : : #include "qgscurve.h"
23 : : #include "qgslinestring.h"
24 : : #include "qgsgeometryengine.h"
25 : : #include "qgsgeometryutils.h"
26 : : #include "qgsapplication.h"
27 : :
28 : 1 : QgsGeometryMissingVertexCheck::QgsGeometryMissingVertexCheck( const QgsGeometryCheckContext *context, const QVariantMap &geometryCheckConfiguration )
29 : 1 : : QgsGeometryCheck( context, geometryCheckConfiguration )
30 : :
31 : 2 : {}
32 : :
33 : 1 : void QgsGeometryMissingVertexCheck::collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids ) const
34 : : {
35 : 1 : Q_UNUSED( messages )
36 : 1 : if ( feedback )
37 : 1 : feedback->setProgress( feedback->progress() + 1.0 );
38 : :
39 : 1 : QMap<QString, QgsFeatureIds> featureIds = ids.isEmpty() ? allLayerFeatureIds( featurePools ) : ids.toMap();
40 : :
41 : 1 : QgsFeaturePool *featurePool = featurePools.value( featureIds.firstKey() );
42 : :
43 : 1 : const QgsGeometryCheckerUtils::LayerFeatures layerFeatures( featurePools, featureIds, compatibleGeometryTypes(), nullptr, mContext, true );
44 : :
45 : 7 : for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeature : layerFeatures )
46 : : {
47 : 6 : if ( feedback && feedback->isCanceled() )
48 : : {
49 : 0 : break;
50 : : }
51 : :
52 : 6 : const QgsGeometry geometry = layerFeature.geometry();
53 : 6 : const QgsAbstractGeometry *geom = geometry.constGet();
54 : :
55 : 6 : if ( QgsCurvePolygon *polygon = qgsgeometry_cast<QgsCurvePolygon *>( geom ) )
56 : : {
57 : 0 : processPolygon( polygon, featurePool, errors, layerFeature, feedback );
58 : 0 : }
59 : 6 : else if ( QgsGeometryCollection *collection = qgsgeometry_cast<QgsGeometryCollection *>( geom ) )
60 : : {
61 : 6 : const int numGeometries = collection->numGeometries();
62 : 12 : for ( int i = 0; i < numGeometries; ++i )
63 : : {
64 : 6 : if ( QgsCurvePolygon *polygon = qgsgeometry_cast<QgsCurvePolygon *>( collection->geometryN( i ) ) )
65 : : {
66 : 6 : processPolygon( polygon, featurePool, errors, layerFeature, feedback );
67 : 6 : }
68 : 6 : }
69 : 6 : }
70 : 6 : }
71 : 1 : }
72 : :
73 : 0 : void QgsGeometryMissingVertexCheck::fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> & /*mergeAttributeIndices*/, Changes &changes ) const
74 : : {
75 : 0 : Q_UNUSED( featurePools )
76 : 0 : Q_UNUSED( changes )
77 : :
78 : 0 : QMetaEnum metaEnum = QMetaEnum::fromType<QgsGeometryMissingVertexCheck::ResolutionMethod>();
79 : 0 : if ( !metaEnum.isValid() || !metaEnum.valueToKey( method ) )
80 : : {
81 : 0 : error->setFixFailed( tr( "Unknown method" ) );
82 : 0 : }
83 : : else
84 : : {
85 : 0 : ResolutionMethod methodValue = static_cast<ResolutionMethod>( method );
86 : 0 : switch ( methodValue )
87 : : {
88 : : case NoChange:
89 : 0 : error->setFixed( method );
90 : 0 : break;
91 : :
92 : : case AddMissingVertex:
93 : : {
94 : 0 : QgsFeaturePool *featurePool = featurePools[ error->layerId() ];
95 : :
96 : 0 : QgsFeature feature;
97 : 0 : featurePool->getFeature( error->featureId(), feature );
98 : :
99 : 0 : QgsPointXY pointOnSegment; // Should be equal to location
100 : : int vertexIndex;
101 : 0 : QgsGeometry geometry = feature.geometry();
102 : 0 : geometry.closestSegmentWithContext( error->location(), pointOnSegment, vertexIndex );
103 : 0 : geometry.insertVertex( QgsPoint( error->location() ), vertexIndex );
104 : 0 : feature.setGeometry( geometry );
105 : :
106 : 0 : featurePool->updateFeature( feature );
107 : : // TODO update "changes" structure
108 : :
109 : 0 : error->setFixed( method );
110 : 0 : }
111 : 0 : break;
112 : : }
113 : : }
114 : 0 : }
115 : :
116 : 0 : QStringList QgsGeometryMissingVertexCheck::resolutionMethods() const
117 : : {
118 : 0 : static QStringList methods = QStringList()
119 : 0 : << tr( "No action" )
120 : 0 : << tr( "Add missing vertex" );
121 : 0 : return methods;
122 : 0 : }
123 : :
124 : 0 : QString QgsGeometryMissingVertexCheck::description() const
125 : : {
126 : 0 : return factoryDescription();
127 : : }
128 : :
129 : 6 : void QgsGeometryMissingVertexCheck::processPolygon( const QgsCurvePolygon *polygon, QgsFeaturePool *featurePool, QList<QgsGeometryCheckError *> &errors, const QgsGeometryCheckerUtils::LayerFeature &layerFeature, QgsFeedback *feedback ) const
130 : : {
131 : 6 : const QgsFeature ¤tFeature = layerFeature.feature();
132 : 6 : std::unique_ptr<QgsMultiPolygon> boundaries = std::make_unique<QgsMultiPolygon>();
133 : :
134 : 6 : std::unique_ptr< QgsGeometryEngine > geomEngine = QgsGeometryCheckerUtils::createGeomEngine( polygon->exteriorRing()->clone(), mContext->tolerance );
135 : 6 : boundaries->addGeometry( geomEngine->buffer( mContext->tolerance, 5 ) );
136 : :
137 : 6 : const int numRings = polygon->numInteriorRings();
138 : 6 : for ( int i = 0; i < numRings; ++i )
139 : : {
140 : 0 : geomEngine = QgsGeometryCheckerUtils::createGeomEngine( polygon->interiorRing( i ), mContext->tolerance );
141 : 0 : boundaries->addGeometry( geomEngine->buffer( mContext->tolerance, 5 ) );
142 : 0 : }
143 : :
144 : 6 : geomEngine = QgsGeometryCheckerUtils::createGeomEngine( boundaries.get(), mContext->tolerance );
145 : 6 : geomEngine->prepareGeometry();
146 : :
147 : 6 : const QgsFeatureIds fids = featurePool->getIntersects( boundaries->boundingBox() );
148 : :
149 : 6 : QgsFeature compareFeature;
150 : 32 : for ( QgsFeatureId fid : fids )
151 : : {
152 : 26 : if ( fid == currentFeature.id() )
153 : 6 : continue;
154 : :
155 : 20 : if ( featurePool->getFeature( fid, compareFeature ) )
156 : : {
157 : 20 : if ( feedback && feedback->isCanceled() )
158 : 0 : break;
159 : :
160 : 20 : const QgsGeometry compareGeometry = compareFeature.geometry();
161 : 20 : QgsVertexIterator vertexIterator = compareGeometry.vertices();
162 : 152 : while ( vertexIterator.hasNext() )
163 : : {
164 : 132 : const QgsPoint &pt = vertexIterator.next();
165 : 132 : if ( geomEngine->intersects( &pt ) )
166 : : {
167 : 48 : QgsVertexId vertexId;
168 : 48 : QgsPoint closestVertex = QgsGeometryUtils::closestVertex( *polygon, pt, vertexId );
169 : :
170 : 48 : if ( closestVertex.distance( pt ) > mContext->tolerance )
171 : : {
172 : 6 : bool alreadyReported = false;
173 : 19 : for ( QgsGeometryCheckError *error : std::as_const( errors ) )
174 : : {
175 : : // Only list missing vertices once
176 : 16 : if ( error->featureId() == currentFeature.id() && error->location() == QgsPointXY( pt ) )
177 : : {
178 : 1 : alreadyReported = true;
179 : 1 : break;
180 : : }
181 : : }
182 : 6 : if ( !alreadyReported )
183 : : {
184 : 5 : std::unique_ptr<QgsGeometryMissingVertexCheckError> error = std::make_unique<QgsGeometryMissingVertexCheckError>( this, layerFeature, QgsPointXY( pt ) );
185 : 5 : error->setAffectedAreaBBox( contextBoundingBox( polygon, vertexId, pt ) );
186 : 5 : QMap<QString, QgsFeatureIds> involvedFeatures;
187 : 5 : involvedFeatures[layerFeature.layerId()].insert( layerFeature.feature().id() );
188 : 5 : involvedFeatures[featurePool->layerId()].insert( fid );
189 : 5 : error->setInvolvedFeatures( involvedFeatures );
190 : :
191 : 5 : errors.append( error.release() );
192 : 5 : }
193 : 6 : }
194 : 48 : }
195 : 132 : }
196 : 20 : }
197 : : }
198 : 6 : }
199 : :
200 : 5 : QgsRectangle QgsGeometryMissingVertexCheck::contextBoundingBox( const QgsCurvePolygon *polygon, const QgsVertexId &vertexId, const QgsPoint &point ) const
201 : : {
202 : 5 : QgsVertexId vertexBefore;
203 : 5 : QgsVertexId vertexAfter;
204 : :
205 : 5 : polygon->adjacentVertices( vertexId, vertexBefore, vertexAfter );
206 : :
207 : 5 : QgsPoint ptBefore = polygon->vertexAt( vertexBefore );
208 : 5 : QgsPoint ptAt = polygon->vertexAt( vertexId );
209 : 5 : QgsPoint ptAfter = polygon->vertexAt( vertexAfter );
210 : :
211 : 5 : double length = std::abs( ptAt.distance( ptBefore ) ) + std::abs( ptAt.distance( ptAfter ) );
212 : :
213 : 5 : QgsRectangle rect( point.x() - length / 2, point.y() - length / 2, point.x() + length / 2, point.y() + length / 2 );
214 : : return rect;
215 : 5 : }
216 : :
217 : 0 : QString QgsGeometryMissingVertexCheck::id() const
218 : : {
219 : 0 : return factoryId();
220 : : }
221 : :
222 : 1 : QList<QgsWkbTypes::GeometryType> QgsGeometryMissingVertexCheck::compatibleGeometryTypes() const
223 : : {
224 : 1 : return factoryCompatibleGeometryTypes();
225 : : }
226 : :
227 : 0 : QgsGeometryCheck::Flags QgsGeometryMissingVertexCheck::flags() const
228 : : {
229 : 0 : return factoryFlags();
230 : : }
231 : :
232 : 0 : QgsGeometryCheck::CheckType QgsGeometryMissingVertexCheck::checkType() const
233 : : {
234 : 0 : return factoryCheckType();
235 : : }
236 : :
237 : : ///@cond private
238 : 1 : QList<QgsWkbTypes::GeometryType> QgsGeometryMissingVertexCheck::factoryCompatibleGeometryTypes()
239 : : {
240 : 1 : return {QgsWkbTypes::PolygonGeometry};
241 : : }
242 : :
243 : 0 : bool QgsGeometryMissingVertexCheck::factoryIsCompatible( QgsVectorLayer *layer ) SIP_SKIP
244 : : {
245 : 0 : return factoryCompatibleGeometryTypes().contains( layer->geometryType() );
246 : 0 : }
247 : :
248 : 0 : QString QgsGeometryMissingVertexCheck::factoryDescription()
249 : : {
250 : 0 : return tr( "Missing Vertex" );
251 : : }
252 : :
253 : 0 : QString QgsGeometryMissingVertexCheck::factoryId()
254 : : {
255 : 0 : return QStringLiteral( "QgsGeometryMissingVertexCheck" );
256 : : }
257 : :
258 : 0 : QgsGeometryCheck::Flags QgsGeometryMissingVertexCheck::factoryFlags()
259 : : {
260 : 0 : return QgsGeometryCheck::AvailableInValidation;
261 : : }
262 : :
263 : 0 : QgsGeometryCheck::CheckType QgsGeometryMissingVertexCheck::factoryCheckType()
264 : : {
265 : 0 : return QgsGeometryCheck::LayerCheck;
266 : : }
267 : : ///@endcond private
268 : :
269 : 5 : QgsGeometryMissingVertexCheckError::QgsGeometryMissingVertexCheckError( const QgsGeometryCheck *check, const QgsGeometryCheckerUtils::LayerFeature &layerFeature, const QgsPointXY &errorLocation, QgsVertexId vidx, const QVariant &value, QgsGeometryCheckError::ValueType valueType )
270 : 5 : : QgsGeometryCheckError( check, layerFeature, errorLocation, vidx, value, valueType )
271 : 10 : {
272 : 5 : }
273 : :
274 : 0 : QgsRectangle QgsGeometryMissingVertexCheckError::affectedAreaBBox() const
275 : : {
276 : 0 : return mAffectedAreaBBox;
277 : : }
278 : :
279 : 5 : void QgsGeometryMissingVertexCheckError::setAffectedAreaBBox( const QgsRectangle &affectedAreaBBox )
280 : : {
281 : 5 : mAffectedAreaBBox = affectedAreaBBox;
282 : 5 : }
283 : :
284 : 0 : QMap<QString, QgsFeatureIds> QgsGeometryMissingVertexCheckError::involvedFeatures() const
285 : : {
286 : 0 : return mInvolvedFeatures;
287 : : }
288 : :
289 : 5 : void QgsGeometryMissingVertexCheckError::setInvolvedFeatures( const QMap<QString, QgsFeatureIds> &involvedFeatures )
290 : : {
291 : 5 : mInvolvedFeatures = involvedFeatures;
292 : 5 : }
293 : :
294 : 0 : QIcon QgsGeometryMissingVertexCheckError::icon() const
295 : : {
296 : :
297 : 0 : if ( status() == QgsGeometryCheckError::StatusFixed )
298 : 0 : return QgsApplication::getThemeIcon( QStringLiteral( "/algorithms/mAlgorithmCheckGeometry.svg" ) );
299 : : else
300 : 0 : return QgsApplication::getThemeIcon( QStringLiteral( "/checks/MissingVertex.svg" ) );
301 : 0 : }
|