Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgsgeometryareacheck.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 "qgsgeometrycollection.h"
19 : : #include "qgsgeometryareacheck.h"
20 : : #include "qgsfeaturepool.h"
21 : : #include "qgsgeometrycheckerror.h"
22 : :
23 : 2 : void QgsGeometryAreaCheck::collectErrors( const QMap<QString, QgsFeaturePool *> &featurePools, QList<QgsGeometryCheckError *> &errors, QStringList &messages, QgsFeedback *feedback, const LayerFeatureIds &ids ) const
24 : : {
25 : 2 : Q_UNUSED( messages )
26 : 2 : QMap<QString, QgsFeatureIds> featureIds = ids.isEmpty() ? allLayerFeatureIds( featurePools ) : ids.toMap();
27 : 2 : QgsGeometryCheckerUtils::LayerFeatures layerFeatures( featurePools, featureIds, compatibleGeometryTypes(), feedback, mContext );
28 : 52 : for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeature : layerFeatures )
29 : : {
30 : 50 : const QgsAbstractGeometry *geom = layerFeature.geometry().constGet();
31 : 50 : double layerToMapUnits = scaleFactor( layerFeature.layer() );
32 : 102 : for ( int iPart = 0, nParts = geom->partCount(); iPart < nParts; ++iPart )
33 : : {
34 : : double value;
35 : 52 : const QgsAbstractGeometry *part = QgsGeometryCheckerUtils::getGeomPart( geom, iPart );
36 : 52 : if ( checkThreshold( layerToMapUnits, part, value ) )
37 : : {
38 : 10 : errors.append( new QgsGeometryCheckError( this, layerFeature, part->centroid(), QgsVertexId( iPart ), value * layerToMapUnits * layerToMapUnits, QgsGeometryCheckError::ValueArea ) );
39 : 10 : }
40 : 52 : }
41 : : }
42 : 2 : }
43 : :
44 : 4 : void QgsGeometryAreaCheck::fixError( const QMap<QString, QgsFeaturePool *> &featurePools, QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const
45 : : {
46 : 4 : QgsFeaturePool *featurePool = featurePools[ error->layerId() ];
47 : 4 : QgsFeature feature;
48 : 4 : if ( !featurePool->getFeature( error->featureId(), feature ) )
49 : : {
50 : 0 : error->setObsolete();
51 : 0 : return;
52 : : }
53 : :
54 : 4 : const QgsGeometry g = feature.geometry();
55 : 4 : const QgsAbstractGeometry *geom = g.constGet();
56 : 4 : QgsVertexId vidx = error->vidx();
57 : :
58 : 4 : double layerToMapUnits = scaleFactor( featurePool->layer() );
59 : :
60 : : // Check if polygon still exists
61 : 4 : if ( !vidx.isValid( geom ) )
62 : : {
63 : 0 : error->setObsolete();
64 : 0 : return;
65 : : }
66 : :
67 : : // Check if error still applies
68 : : double value;
69 : 4 : if ( !checkThreshold( layerToMapUnits, QgsGeometryCheckerUtils::getGeomPart( geom, vidx.part ), value ) )
70 : : {
71 : 0 : error->setObsolete();
72 : 0 : return;
73 : : }
74 : :
75 : : // Fix with selected method
76 : 4 : if ( method == NoChange )
77 : : {
78 : 0 : error->setFixed( method );
79 : 0 : }
80 : 4 : else if ( method == Delete )
81 : : {
82 : 1 : deleteFeatureGeometryPart( featurePools, error->layerId(), feature, vidx.part, changes );
83 : 1 : error->setFixed( method );
84 : 1 : }
85 : 3 : else if ( method == MergeLongestEdge || method == MergeLargestArea || method == MergeIdenticalAttribute )
86 : : {
87 : 3 : QString errMsg;
88 : 3 : if ( mergeWithNeighbor( featurePools, error->layerId(), feature, vidx.part, method, mergeAttributeIndices[error->layerId()], changes, errMsg ) )
89 : : {
90 : 3 : error->setFixed( method );
91 : 3 : }
92 : : else
93 : : {
94 : 0 : error->setFixFailed( tr( "Failed to merge with neighbor: %1" ).arg( errMsg ) );
95 : : }
96 : 3 : }
97 : : else
98 : : {
99 : 0 : error->setFixFailed( tr( "Unknown method" ) );
100 : : }
101 : 4 : }
102 : :
103 : 30 : bool QgsGeometryAreaCheck::checkThreshold( double layerToMapUnits, const QgsAbstractGeometry *geom, double &value ) const
104 : : {
105 : 30 : value = geom->area();
106 : 30 : double threshold = mAreaThreshold / ( layerToMapUnits * layerToMapUnits );
107 : 30 : return value < threshold;
108 : : }
109 : :
110 : 3 : bool QgsGeometryAreaCheck::mergeWithNeighbor( const QMap<QString, QgsFeaturePool *> &featurePools,
111 : : const QString &layerId, QgsFeature &feature,
112 : : int partIdx, int method, int mergeAttributeIndex, Changes &changes, QString &errMsg ) const
113 : : {
114 : 3 : QgsFeaturePool *featurePool = featurePools[ layerId ];
115 : :
116 : 3 : double maxVal = 0.;
117 : 3 : QgsFeature mergeFeature;
118 : 3 : int mergePartIdx = -1;
119 : 3 : bool matchFound = false;
120 : 3 : QgsGeometry featureGeometry = feature.geometry();
121 : 3 : const QgsAbstractGeometry *geom = featureGeometry.constGet();
122 : :
123 : : // Search for touching neighboring geometries
124 : 3 : const QgsFeatureIds intersects = featurePool->getIntersects( featureGeometry.boundingBox() );
125 : 12 : for ( QgsFeatureId testId : intersects )
126 : : {
127 : 10 : QgsFeature testFeature;
128 : 10 : if ( !featurePool->getFeature( testId, testFeature ) )
129 : : {
130 : 0 : continue;
131 : : }
132 : 10 : QgsGeometry testFeatureGeom = testFeature.geometry();
133 : 10 : const QgsAbstractGeometry *testGeom = testFeatureGeom.constGet();
134 : 19 : for ( int testPartIdx = 0, nTestParts = testGeom->partCount(); testPartIdx < nTestParts; ++testPartIdx )
135 : : {
136 : 10 : if ( testId == feature.id() && testPartIdx == partIdx )
137 : : {
138 : 2 : continue;
139 : : }
140 : 8 : double len = QgsGeometryCheckerUtils::sharedEdgeLength( QgsGeometryCheckerUtils::getGeomPart( geom, partIdx ), QgsGeometryCheckerUtils::getGeomPart( testGeom, testPartIdx ), mContext->reducedTolerance );
141 : 8 : if ( len > 0. )
142 : : {
143 : 8 : if ( method == MergeLongestEdge || method == MergeLargestArea )
144 : : {
145 : : double val;
146 : 6 : if ( method == MergeLongestEdge )
147 : : {
148 : 3 : val = len;
149 : 3 : }
150 : : else
151 : : {
152 : 3 : if ( dynamic_cast<const QgsGeometryCollection *>( testGeom ) )
153 : 3 : val = static_cast<const QgsGeometryCollection *>( testGeom )->geometryN( testPartIdx )->area();
154 : : else
155 : 0 : val = testGeom->area();
156 : : }
157 : 6 : if ( val > maxVal )
158 : : {
159 : 4 : maxVal = val;
160 : 4 : mergeFeature = testFeature;
161 : 4 : mergePartIdx = testPartIdx;
162 : 4 : }
163 : 6 : }
164 : 2 : else if ( method == MergeIdenticalAttribute )
165 : : {
166 : 2 : if ( testFeature.attribute( mergeAttributeIndex ) == feature.attribute( mergeAttributeIndex ) )
167 : : {
168 : 1 : mergeFeature = testFeature;
169 : 1 : mergePartIdx = testPartIdx;
170 : 1 : matchFound = true;
171 : 1 : break;
172 : : }
173 : 1 : }
174 : 7 : }
175 : 7 : }
176 : 10 : if ( matchFound )
177 : : {
178 : 1 : break;
179 : : }
180 : 10 : }
181 : :
182 : 3 : if ( !matchFound && maxVal == 0. )
183 : : {
184 : 0 : return method == MergeIdenticalAttribute;
185 : : }
186 : :
187 : : // Merge geometries
188 : 3 : QgsGeometry mergeFeatureGeom = mergeFeature.geometry();
189 : 3 : const QgsAbstractGeometry *mergeGeom = mergeFeatureGeom.constGet();
190 : 3 : std::unique_ptr<QgsGeometryEngine> geomEngine( QgsGeometryCheckerUtils::createGeomEngine( QgsGeometryCheckerUtils::getGeomPart( mergeGeom, mergePartIdx ), mContext->reducedTolerance ) );
191 : 3 : QgsAbstractGeometry *combinedGeom = geomEngine->combine( QgsGeometryCheckerUtils::getGeomPart( geom, partIdx ), &errMsg );
192 : 3 : if ( !combinedGeom || combinedGeom->isEmpty() || !QgsWkbTypes::isSingleType( combinedGeom->wkbType() ) )
193 : : {
194 : 0 : return false;
195 : : }
196 : :
197 : : // Replace polygon in merge geometry
198 : 3 : if ( mergeFeature.id() == feature.id() && mergePartIdx > partIdx )
199 : : {
200 : 0 : --mergePartIdx;
201 : 0 : }
202 : 3 : replaceFeatureGeometryPart( featurePools, layerId, mergeFeature, mergePartIdx, combinedGeom, changes );
203 : : // Remove polygon from source geometry
204 : 3 : deleteFeatureGeometryPart( featurePools, layerId, feature, partIdx, changes );
205 : :
206 : 3 : return true;
207 : 3 : }
208 : :
209 : 4 : QStringList QgsGeometryAreaCheck::resolutionMethods() const
210 : : {
211 : 5 : static QStringList methods = QStringList()
212 : 1 : << tr( "Merge with neighboring polygon with longest shared edge" )
213 : 1 : << tr( "Merge with neighboring polygon with largest area" )
214 : 1 : << tr( "Merge with neighboring polygon with identical attribute value, if any, or leave as is" )
215 : 1 : << tr( "Delete feature" )
216 : 1 : << tr( "No action" );
217 : 4 : return methods;
218 : 0 : }
|