Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgsgeometryanglecheck.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 "qgsgeometryanglecheck.h"
18 : : #include "qgsgeometryutils.h"
19 : : #include "qgsfeaturepool.h"
20 : : #include "qgsgeometrycheckerror.h"
21 : :
22 : 1 : QList<QgsWkbTypes::GeometryType> QgsGeometryAngleCheck::compatibleGeometryTypes() const
23 : : {
24 : 1 : return factoryCompatibleGeometryTypes();
25 : : }
26 : :
27 : 1 : void QgsGeometryAngleCheck::collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids ) const
28 : : {
29 : 1 : Q_UNUSED( messages )
30 : 1 : QMap<QString, QgsFeatureIds> featureIds = ids.isEmpty() ? allLayerFeatureIds( featurePools ) : ids.toMap();
31 : 1 : QgsGeometryCheckerUtils::LayerFeatures layerFeatures( featurePools, featureIds, compatibleGeometryTypes(), feedback, context() );
32 : 35 : for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeature : layerFeatures )
33 : : {
34 : 34 : const QgsAbstractGeometry *geom = layerFeature.geometry().constGet();
35 : 70 : for ( int iPart = 0, nParts = geom->partCount(); iPart < nParts; ++iPart )
36 : : {
37 : 73 : for ( int iRing = 0, nRings = geom->ringCount( iPart ); iRing < nRings; ++iRing )
38 : : {
39 : 37 : bool closed = false;
40 : 37 : int nVerts = QgsGeometryCheckerUtils::polyLineSize( geom, iPart, iRing, &closed );
41 : : // Less than three points, no angles to check
42 : 37 : if ( nVerts < 3 )
43 : : {
44 : 1 : continue;
45 : : }
46 : 193 : for ( int iVert = !closed; iVert < nVerts - !closed; ++iVert )
47 : : {
48 : 157 : const QgsPoint &p1 = geom->vertexAt( QgsVertexId( iPart, iRing, ( iVert - 1 + nVerts ) % nVerts ) );
49 : 157 : const QgsPoint &p2 = geom->vertexAt( QgsVertexId( iPart, iRing, iVert ) );
50 : 157 : const QgsPoint &p3 = geom->vertexAt( QgsVertexId( iPart, iRing, ( iVert + 1 ) % nVerts ) );
51 : 157 : QgsVector v21, v23;
52 : : try
53 : : {
54 : 157 : v21 = QgsVector( p1.x() - p2.x(), p1.y() - p2.y() ).normalized();
55 : 155 : v23 = QgsVector( p3.x() - p2.x(), p3.y() - p2.y() ).normalized();
56 : 157 : }
57 : : catch ( const QgsException & )
58 : : {
59 : : // Zero length vectors
60 : : continue;
61 : 4 : }
62 : :
63 : 153 : double angle = std::acos( v21 * v23 ) / M_PI * 180.0;
64 : 153 : if ( angle < mMinAngle )
65 : : {
66 : 8 : errors.append( new QgsGeometryCheckError( this, layerFeature, p2, QgsVertexId( iPart, iRing, iVert ), angle ) );
67 : 8 : }
68 : 157 : }
69 : 36 : }
70 : 36 : }
71 : : }
72 : 5 : }
73 : :
74 : 2 : void QgsGeometryAngleCheck::fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> & /*mergeAttributeIndices*/, Changes &changes ) const
75 : : {
76 : 2 : QgsFeaturePool *featurePool = featurePools[ error->layerId() ];
77 : 2 : QgsFeature feature;
78 : 2 : if ( !featurePool->getFeature( error->featureId(), feature ) )
79 : : {
80 : 0 : error->setObsolete();
81 : 0 : return;
82 : : }
83 : 2 : QgsGeometry featureGeometry = feature.geometry();
84 : 2 : QgsAbstractGeometry *geometry = featureGeometry.get();
85 : 2 : QgsVertexId vidx = error->vidx();
86 : :
87 : : // Check if point still exists
88 : 2 : if ( !vidx.isValid( geometry ) )
89 : : {
90 : 0 : error->setObsolete();
91 : 0 : return;
92 : : }
93 : :
94 : : // Check if error still applies
95 : 2 : int n = QgsGeometryCheckerUtils::polyLineSize( geometry, vidx.part, vidx.ring );
96 : 2 : if ( n == 0 )
97 : : {
98 : 0 : error->setObsolete();
99 : 0 : return;
100 : : }
101 : 2 : const QgsPoint &p1 = geometry->vertexAt( QgsVertexId( vidx.part, vidx.ring, ( vidx.vertex - 1 + n ) % n ) );
102 : 2 : const QgsPoint &p2 = geometry->vertexAt( vidx );
103 : 2 : const QgsPoint &p3 = geometry->vertexAt( QgsVertexId( vidx.part, vidx.ring, ( vidx.vertex + 1 ) % n ) );
104 : 2 : QgsVector v21, v23;
105 : : try
106 : : {
107 : 2 : v21 = QgsVector( p1.x() - p2.x(), p1.y() - p2.y() ).normalized();
108 : 2 : v23 = QgsVector( p3.x() - p2.x(), p3.y() - p2.y() ).normalized();
109 : 2 : }
110 : : catch ( const QgsException & )
111 : : {
112 : 0 : error->setObsolete();
113 : : return;
114 : 0 : }
115 : 2 : double angle = std::acos( v21 * v23 ) / M_PI * 180.0;
116 : 2 : if ( angle >= mMinAngle )
117 : : {
118 : 0 : error->setObsolete();
119 : 0 : return;
120 : : }
121 : :
122 : : // Fix error
123 : 2 : if ( method == NoChange )
124 : : {
125 : 0 : error->setFixed( method );
126 : 0 : }
127 : 2 : else if ( method == DeleteNode )
128 : : {
129 : 2 : if ( !QgsGeometryCheckerUtils::canDeleteVertex( geometry, vidx.part, vidx.ring ) )
130 : : {
131 : 0 : error->setFixFailed( tr( "Resulting geometry is degenerate" ) );
132 : 0 : }
133 : 2 : else if ( !geometry->deleteVertex( error->vidx() ) )
134 : : {
135 : 0 : error->setFixFailed( tr( "Failed to delete vertex" ) );
136 : 0 : }
137 : : else
138 : : {
139 : 2 : changes[error->layerId()][error->featureId()].append( Change( ChangeNode, ChangeRemoved, vidx ) );
140 : : // Avoid duplicate nodes as result of deleting spike vertex
141 : 2 : if ( QgsGeometryUtils::sqrDistance2D( p1, p3 ) < mContext->tolerance &&
142 : 0 : QgsGeometryCheckerUtils::canDeleteVertex( geometry, vidx.part, vidx.ring ) &&
143 : 0 : geometry->deleteVertex( error->vidx() ) ) // error->vidx points to p3 after removing p2
144 : : {
145 : 0 : changes[error->layerId()][error->featureId()].append( Change( ChangeNode, ChangeRemoved, QgsVertexId( vidx.part, vidx.ring, ( vidx.vertex + 1 ) % n ) ) );
146 : 0 : }
147 : 2 : feature.setGeometry( featureGeometry );
148 : 2 : featurePool->updateFeature( feature );
149 : 2 : error->setFixed( method );
150 : : }
151 : 2 : }
152 : : else
153 : : {
154 : 0 : error->setFixFailed( tr( "Unknown method" ) );
155 : : }
156 : 2 : }
157 : :
158 : 2 : QStringList QgsGeometryAngleCheck::resolutionMethods() const
159 : : {
160 : 2 : static QStringList methods = QStringList() << tr( "Delete node with small angle" ) << tr( "No action" );
161 : 2 : return methods;
162 : 0 : }
163 : :
164 : 0 : QString QgsGeometryAngleCheck::id() const
165 : : {
166 : 0 : return factoryId();
167 : : }
168 : :
169 : 0 : QString QgsGeometryAngleCheck::factoryDescription()
170 : : {
171 : 0 : return tr( "Minimal angle" );
172 : : }
173 : :
174 : 0 : QString QgsGeometryAngleCheck::description() const
175 : : {
176 : 0 : return factoryDescription();
177 : : }
178 : :
179 : 1 : QgsGeometryCheck::CheckType QgsGeometryAngleCheck::checkType() const
180 : : {
181 : 1 : return factoryCheckType();
182 : : }
183 : :
184 : 1 : QList<QgsWkbTypes::GeometryType> QgsGeometryAngleCheck::factoryCompatibleGeometryTypes()
185 : : {
186 : 1 : return {QgsWkbTypes::LineGeometry, QgsWkbTypes::PolygonGeometry};
187 : : }
188 : :
189 : 0 : bool QgsGeometryAngleCheck::factoryIsCompatible( QgsVectorLayer *layer )
190 : : {
191 : 0 : return factoryCompatibleGeometryTypes().contains( layer->geometryType() );
192 : 0 : }
193 : :
194 : 0 : QString QgsGeometryAngleCheck::factoryId()
195 : : {
196 : 0 : return QStringLiteral( "QgsGeometryAngleCheck" );
197 : : }
198 : :
199 : 1 : QgsGeometryCheck::CheckType QgsGeometryAngleCheck::factoryCheckType()
200 : : {
201 : 1 : return QgsGeometryCheck::FeatureNodeCheck;
202 : : }
|