Branch data Line data Source code
1 : : /***************************************************************************
2 : : testqgsgeometrychecks.cpp
3 : : --------------------------------------
4 : : Date : September 2017
5 : : Copyright : (C) 2017 Sandro Mani
6 : : Email : manisandro at gmail dot com
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 "qgstest.h"
17 : : #include "qgsfeature.h"
18 : : #include "qgsfeaturepool.h"
19 : : #include "qgsvectorlayer.h"
20 : :
21 : : #include "qgsgeometryanglecheck.h"
22 : : #include "qgsgeometryareacheck.h"
23 : : #include "qgsgeometrycontainedcheck.h"
24 : : #include "qgsgeometrydanglecheck.h"
25 : : #include "qgsgeometrydegeneratepolygoncheck.h"
26 : : #include "qgsgeometryduplicatecheck.h"
27 : : #include "qgsgeometryduplicatenodescheck.h"
28 : : #include "qgsgeometryfollowboundariescheck.h"
29 : : #include "qgsgeometrygapcheck.h"
30 : : #include "qgsgeometryholecheck.h"
31 : : #include "qgsgeometrymissingvertexcheck.h"
32 : : #include "qgsgeometrylineintersectioncheck.h"
33 : : #include "qgsgeometrylinelayerintersectioncheck.h"
34 : : #include "qgsgeometrymultipartcheck.h"
35 : : #include "qgsgeometryoverlapcheck.h"
36 : : #include "qgsgeometrypointcoveredbylinecheck.h"
37 : : #include "qgsgeometrypointinpolygoncheck.h"
38 : : #include "qgsgeometrysegmentlengthcheck.h"
39 : : #include "qgsgeometryselfcontactcheck.h"
40 : : #include "qgsgeometryselfintersectioncheck.h"
41 : : #include "qgsgeometrysliverpolygoncheck.h"
42 : : #include "qgsgeos.h"
43 : : #include "qgsvectordataproviderfeaturepool.h"
44 : : #include "qgsmultilinestring.h"
45 : : #include "qgslinestring.h"
46 : : #include "qgsproject.h"
47 : : #include "qgsfeedback.h"
48 : :
49 : : #include "qgsgeometrytypecheck.h"
50 : :
51 : 2 : class TestQgsGeometryChecks: public QObject
52 : : {
53 : : Q_OBJECT
54 : : private:
55 : 114 : struct Change
56 : : {
57 : : QString layerId;
58 : : QgsFeatureId fid;
59 : : QgsGeometryCheck::ChangeWhat what;
60 : : QgsGeometryCheck::ChangeType type;
61 : : QgsVertexId vidx;
62 : : };
63 : : double layerToMapUnits( const QgsMapLayer *layer, const QgsCoordinateReferenceSystem &mapCrs ) const;
64 : : QgsFeaturePool *createFeaturePool( QgsVectorLayer *layer, bool selectedOnly = false ) const;
65 : : QPair<QgsGeometryCheckContext *, QMap<QString, QgsFeaturePool *> > createTestContext( QTemporaryDir &tempDir, QMap<QString, QString> &layers, const QgsCoordinateReferenceSystem &mapCrs = QgsCoordinateReferenceSystem( "EPSG:4326" ), double prec = 8 ) const;
66 : : void cleanupTestContext( QPair<QgsGeometryCheckContext *, QMap<QString, QgsFeaturePool *> > ctx ) const;
67 : : void listErrors( const QList<QgsGeometryCheckError *> &checkErrors, const QStringList &messages ) const;
68 : : QList<QgsGeometryCheckError *> searchCheckErrors( const QList<QgsGeometryCheckError *> &checkErrors, const QString &layerId, const QgsFeatureId &featureId = -1, const QgsPointXY &pos = QgsPointXY(), const QgsVertexId &vid = QgsVertexId(), const QVariant &value = QVariant(), double tol = 1E-4 ) const;
69 : : bool fixCheckError( QMap<QString, QgsFeaturePool *> featurePools, QgsGeometryCheckError *error, int method, const QgsGeometryCheckError::Status &expectedStatus, const QVector<Change> &expectedChanges, const QMap<QString, int> &mergeAttr = QMap<QString, int>() );
70 : : QgsGeometryCheck::Changes change2changes( const Change &change ) const;
71 : :
72 : : private slots:
73 : : // will be called before the first testfunction is executed.
74 : : void initTestCase();
75 : : // will be called after the last testfunction is executed.
76 : : void cleanupTestCase();
77 : :
78 : : private slots:
79 : : void testAngleCheck();
80 : : void testAreaCheck();
81 : : void testContainedCheck();
82 : : void testDangleCheck();
83 : : void testDegeneratePolygonCheck();
84 : : void testDuplicateCheck();
85 : : void testDuplicateNodesCheck();
86 : : void testFollowBoundariesCheck();
87 : : void testGapCheck();
88 : : void testAllowedGaps();
89 : : void testMissingVertexCheck();
90 : : void testHoleCheck();
91 : : void testLineIntersectionCheck();
92 : : void testLineLayerIntersectionCheck();
93 : : void testMultipartCheck();
94 : : void testOverlapCheck();
95 : : void testOverlapCheckNoMaxArea();
96 : : void testPointCoveredByLineCheck();
97 : : void testPointInPolygonCheck();
98 : : void testSegmentLengthCheck();
99 : : void testSelfContactCheck();
100 : : void testSelfIntersectionCheck();
101 : : void testSliverPolygonCheck();
102 : : void testGapCheckPointInPoly();
103 : : void testOverlapCheckToleranceBug();
104 : : };
105 : :
106 : 1 : void TestQgsGeometryChecks::initTestCase()
107 : : {
108 : 1 : QgsApplication::initQgis();
109 : 1 : }
110 : :
111 : 1 : void TestQgsGeometryChecks::cleanupTestCase()
112 : : {
113 : 1 : QgsApplication::exitQgis();
114 : 1 : }
115 : :
116 : : ///////////////////////////////////////////////////////////////////////////////
117 : :
118 : 1 : void TestQgsGeometryChecks::testAngleCheck()
119 : : {
120 : 1 : QTemporaryDir dir;
121 : 1 : QMap<QString, QString> layers;
122 : 1 : layers.insert( "point_layer.shp", "" );
123 : 1 : layers.insert( "line_layer.shp", "" );
124 : 1 : layers.insert( "polygon_layer.shp", "" );
125 : 1 : auto testContext = createTestContext( dir, layers );
126 : :
127 : : // Test detection
128 : 1 : QList<QgsGeometryCheckError *> checkErrors;
129 : 1 : QStringList messages;
130 : :
131 : 1 : QVariantMap configuration;
132 : 1 : configuration.insert( "minAngle", 15 );
133 : :
134 : 1 : QgsGeometryAngleCheck check( testContext.first, configuration );
135 : 1 : QgsFeedback feedback;
136 : 1 : check.collectErrors( testContext.second, checkErrors, messages, &feedback );
137 : 1 : listErrors( checkErrors, messages );
138 : :
139 : 1 : QList<QgsGeometryCheckError *> errs1;
140 : 1 : QList<QgsGeometryCheckError *> errs2;
141 : :
142 : 1 : QCOMPARE( checkErrors.size(), 8 );
143 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["point_layer.shp"] ).isEmpty() );
144 : 1 : QVERIFY( ( errs1 = searchCheckErrors( checkErrors, layers["line_layer.shp"], 0, QgsPointXY( -0.2225, 0.5526 ), QgsVertexId( 0, 0, 3 ), 10.5865 ) ).size() == 1 );
145 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"], 0, QgsPointXY( -0.94996, 0.99967 ), QgsVertexId( 1, 0, 1 ), 8.3161 ).size() == 1 );
146 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"], 2, QgsPointXY( -0.4547, -0.3059 ), QgsVertexId( 0, 0, 1 ), 5.4165 ).size() == 1 );
147 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"], 2, QgsPointXY( -0.7594, -0.1971 ), QgsVertexId( 0, 0, 2 ), 12.5288 ).size() == 1 );
148 : 1 : QVERIFY( ( errs2 = searchCheckErrors( checkErrors, layers["polygon_layer.shp"], 0, QgsPointXY( 0.2402, 1.0786 ), QgsVertexId( 0, 0, 1 ), 13.5140 ) ).size() == 1 );
149 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["polygon_layer.shp"], 1, QgsPointXY( 0.6960, 0.5908 ), QgsVertexId( 0, 0, 0 ), 7.0556 ).size() == 1 );
150 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["polygon_layer.shp"], 1, QgsPointXY( 0.98690, 0.55699 ), QgsVertexId( 1, 0, 5 ), 7.7351 ).size() == 1 );
151 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["polygon_layer.shp"], 11, QgsPointXY( -0.3186, 1.6734 ), QgsVertexId( 0, 0, 1 ), 3.5092 ).size() == 1 );
152 : :
153 : : // Test fixes
154 : 1 : QgsFeature f;
155 : : int n1, n2;
156 : :
157 : 1 : testContext.second[errs1[0]->layerId()]->getFeature( errs1[0]->featureId(), f );
158 : 1 : n1 = f.geometry().constGet()->vertexCount( errs1[0]->vidx().part, errs1[0]->vidx().ring );
159 : 1 : QVERIFY( fixCheckError( testContext.second, errs1[0],
160 : : QgsGeometryAngleCheck::DeleteNode, QgsGeometryCheckError::StatusFixed,
161 : : {{errs1[0]->layerId(), errs1[0]->featureId(), QgsGeometryCheck::ChangeNode, QgsGeometryCheck::ChangeRemoved, errs1[0]->vidx()}} ) );
162 : 1 : testContext.second[errs1[0]->layerId()]->getFeature( errs1[0]->featureId(), f );
163 : 1 : n2 = f.geometry().constGet()->vertexCount( errs1[0]->vidx().part, errs1[0]->vidx().ring );
164 : 1 : QCOMPARE( n1, n2 + 1 );
165 : :
166 : 1 : testContext.second[errs2[0]->layerId()]->getFeature( errs2[0]->featureId(), f );
167 : 1 : n1 = f.geometry().constGet()->vertexCount( errs2[0]->vidx().part, errs2[0]->vidx().ring );
168 : 1 : QVERIFY( fixCheckError( testContext.second, errs2[0],
169 : : QgsGeometryAngleCheck::DeleteNode, QgsGeometryCheckError::StatusFixed,
170 : : {{errs2[0]->layerId(), errs2[0]->featureId(), QgsGeometryCheck::ChangeNode, QgsGeometryCheck::ChangeRemoved, errs2[0]->vidx()}} ) );
171 : 1 : testContext.second[errs2[0]->layerId()]->getFeature( errs2[0]->featureId(), f );
172 : 1 : n2 = f.geometry().constGet()->vertexCount( errs2[0]->vidx().part, errs2[0]->vidx().ring );
173 : 1 : QCOMPARE( n1, n2 + 1 );
174 : :
175 : : // Test change tracking
176 : 1 : QVERIFY( !errs2[0]->handleChanges( change2changes( {errs2[0]->layerId(), errs2[0]->featureId(), QgsGeometryCheck::ChangeFeature, QgsGeometryCheck::ChangeRemoved, QgsVertexId()} ) ) );
177 : 1 : QVERIFY( !errs2[0]->handleChanges( change2changes( {errs2[0]->layerId(), errs2[0]->featureId(), QgsGeometryCheck::ChangeFeature, QgsGeometryCheck::ChangeChanged, QgsVertexId()} ) ) );
178 : 1 : QVERIFY( !errs2[0]->handleChanges( change2changes( {errs2[0]->layerId(), errs2[0]->featureId(), QgsGeometryCheck::ChangePart, QgsGeometryCheck::ChangeRemoved, QgsVertexId( errs2[0]->vidx().part )} ) ) );
179 : 1 : QVERIFY( !errs2[0]->handleChanges( change2changes( {errs2[0]->layerId(), errs2[0]->featureId(), QgsGeometryCheck::ChangePart, QgsGeometryCheck::ChangeChanged, QgsVertexId( errs2[0]->vidx().part )} ) ) );
180 : 1 : QVERIFY( errs2[0]->handleChanges( change2changes( {errs2[0]->layerId(), errs2[0]->featureId(), QgsGeometryCheck::ChangePart, QgsGeometryCheck::ChangeRemoved, QgsVertexId( errs2[0]->vidx().part + 1 )} ) ) );
181 : 1 : QVERIFY( errs2[0]->handleChanges( change2changes( {errs2[0]->layerId(), errs2[0]->featureId(), QgsGeometryCheck::ChangePart, QgsGeometryCheck::ChangeChanged, QgsVertexId( errs2[0]->vidx().part + 1 )} ) ) );
182 : 1 : QVERIFY( !errs2[0]->handleChanges( change2changes( {errs2[0]->layerId(), errs2[0]->featureId(), QgsGeometryCheck::ChangeRing, QgsGeometryCheck::ChangeRemoved, QgsVertexId( errs2[0]->vidx().part, errs2[0]->vidx().ring )} ) ) );
183 : 1 : QVERIFY( !errs2[0]->handleChanges( change2changes( {errs2[0]->layerId(), errs2[0]->featureId(), QgsGeometryCheck::ChangeRing, QgsGeometryCheck::ChangeChanged, QgsVertexId( errs2[0]->vidx().part, errs2[0]->vidx().ring )} ) ) );
184 : 1 : QVERIFY( errs2[0]->handleChanges( change2changes( {errs2[0]->layerId(), errs2[0]->featureId(), QgsGeometryCheck::ChangeRing, QgsGeometryCheck::ChangeRemoved, QgsVertexId( errs2[0]->vidx().part, errs2[0]->vidx().ring + 1 )} ) ) );
185 : 1 : QVERIFY( errs2[0]->handleChanges( change2changes( {errs2[0]->layerId(), errs2[0]->featureId(), QgsGeometryCheck::ChangeRing, QgsGeometryCheck::ChangeChanged, QgsVertexId( errs2[0]->vidx().part, errs2[0]->vidx().ring + 1 )} ) ) );
186 : 1 : QVERIFY( !errs2[0]->handleChanges( change2changes( {errs2[0]->layerId(), errs2[0]->featureId(), QgsGeometryCheck::ChangeNode, QgsGeometryCheck::ChangeRemoved, errs2[0]->vidx()} ) ) );
187 : 1 : QVERIFY( !errs2[0]->handleChanges( change2changes( {errs2[0]->layerId(), errs2[0]->featureId(), QgsGeometryCheck::ChangeNode, QgsGeometryCheck::ChangeChanged, errs2[0]->vidx()} ) ) );
188 : 1 : QVERIFY( errs2[0]->handleChanges( change2changes( {errs2[0]->layerId(), errs2[0]->featureId(), QgsGeometryCheck::ChangeNode, QgsGeometryCheck::ChangeRemoved, QgsVertexId( errs2[0]->vidx().part, errs2[0]->vidx().ring, errs2[0]->vidx().vertex + 1 )} ) ) );
189 : 1 : QVERIFY( errs2[0]->handleChanges( change2changes( {errs2[0]->layerId(), errs2[0]->featureId(), QgsGeometryCheck::ChangeNode, QgsGeometryCheck::ChangeChanged, QgsVertexId( errs2[0]->vidx().part, errs2[0]->vidx().ring, errs2[0]->vidx().vertex + 1 )} ) ) );
190 : 1 : QgsVertexId oldVidx = errs2[0]->vidx();
191 : 1 : QVERIFY( errs2[0]->handleChanges( change2changes( {errs2[0]->layerId(), errs2[0]->featureId(), QgsGeometryCheck::ChangeNode, QgsGeometryCheck::ChangeRemoved, QgsVertexId( errs2[0]->vidx().part, errs2[0]->vidx().ring, errs2[0]->vidx().vertex - 1 )} ) ) );
192 : 1 : QVERIFY( errs2[0]->vidx().vertex == oldVidx.vertex - 1 );
193 : :
194 : 1 : cleanupTestContext( testContext );
195 : 1 : }
196 : :
197 : 1 : void TestQgsGeometryChecks::testAreaCheck()
198 : : {
199 : 1 : QTemporaryDir dir;
200 : 1 : QMap<QString, QString> layers;
201 : 1 : layers.insert( "point_layer.shp", "" );
202 : 1 : layers.insert( "line_layer.shp", "" );
203 : 1 : layers.insert( "polygon_layer.shp", "" );
204 : 1 : auto testContext = createTestContext( dir, layers );
205 : :
206 : : // Test detection
207 : 1 : QList<QgsGeometryCheckError *> checkErrors;
208 : 1 : QStringList messages;
209 : :
210 : 1 : QVariantMap configuration;
211 : 1 : configuration.insert( "areaThreshold", 0.04 );
212 : :
213 : 1 : QgsGeometryAreaCheck check( testContext.first, configuration );
214 : 1 : QgsFeedback feedback;
215 : 1 : check.collectErrors( testContext.second, checkErrors, messages, &feedback );
216 : 1 : listErrors( checkErrors, messages );
217 : :
218 : 1 : QList<QgsGeometryCheckError *> errs1;
219 : 1 : QList<QgsGeometryCheckError *> errs2;
220 : 1 : QList<QgsGeometryCheckError *> errs3;
221 : 1 : QList<QgsGeometryCheckError *> errs4;
222 : :
223 : 1 : QCOMPARE( checkErrors.size(), 8 );
224 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["point_layer.shp"] ).isEmpty() );
225 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"] ).isEmpty() );
226 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["polygon_layer.shp"], 1, QgsPointXY( 1.0068, 0.3635 ), QgsVertexId( 1 ), 0.0105 ).size() == 1 );
227 : 1 : QVERIFY( ( errs1 = searchCheckErrors( checkErrors, layers["polygon_layer.shp"], 2, QgsPointXY( 0.9739, 1.0983 ), QgsVertexId( 0 ), 0.0141 ) ).size() == 1 );
228 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["polygon_layer.shp"], 6, QgsPointXY( 0.9968, 1.7584 ), QgsVertexId( 0 ), 0 ).size() == 1 );
229 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["polygon_layer.shp"], 11, QgsPointXY( -0.2941, 1.4614 ), QgsVertexId( 0 ), 0.0031 ).size() == 1 );
230 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["polygon_layer.shp"], 12, QgsPointXY( 0.2229, 0.2306 ), QgsVertexId( 0 ), -0.0079 ).size() == 1 ); // This is a polygon with a self-intersection hence the incorrect negative area
231 : 1 : QVERIFY( ( errs2 = searchCheckErrors( checkErrors, layers["polygon_layer.shp"], 13, QgsPointXY( 0.5026, 3.0267 ), QgsVertexId( 0 ), 0.0013 ) ).size() == 1 );
232 : 1 : QVERIFY( ( errs3 = searchCheckErrors( checkErrors, layers["polygon_layer.shp"], 20, QgsPointXY( 0.4230, 3.4688 ), QgsVertexId( 0 ), 0.0013 ) ).size() == 1 );
233 : 1 : QVERIFY( ( errs4 = searchCheckErrors( checkErrors, layers["polygon_layer.shp"], 24, QgsPointXY( 1.4186, 3.0905 ), QgsVertexId( 0 ), 0.0013 ) ).size() == 1 );
234 : :
235 : : // Test fixes
236 : 1 : QgsFeature f;
237 : : bool valid;
238 : :
239 : 1 : QVERIFY( fixCheckError( testContext.second, errs1[0],
240 : : QgsGeometryAreaCheck::Delete, QgsGeometryCheckError::StatusFixed,
241 : : {{errs1[0]->layerId(), errs1[0]->featureId(), QgsGeometryCheck::ChangeFeature, QgsGeometryCheck::ChangeRemoved, QgsVertexId()}} ) );
242 : 1 : valid = testContext.second[errs1[0]->layerId()]->getFeature( errs1[0]->featureId(), f );
243 : 1 : QVERIFY( !valid );
244 : :
245 : : // Try merging a small geometry by longest edge, largest area and common value
246 : 1 : testContext.second[layers["polygon_layer.shp"]]->getFeature( 15, f );
247 : 1 : double area15 = f.geometry().area();
248 : 3 : QVERIFY( fixCheckError( testContext.second, errs2[0],
249 : : QgsGeometryAreaCheck::MergeLargestArea, QgsGeometryCheckError::StatusFixed,
250 : : {
251 : : {errs2[0]->layerId(), errs2[0]->featureId(), QgsGeometryCheck::ChangeFeature, QgsGeometryCheck::ChangeRemoved, QgsVertexId()},
252 : : {layers["polygon_layer.shp"], 15, QgsGeometryCheck::ChangePart, QgsGeometryCheck::ChangeRemoved, QgsVertexId( 0 )},
253 : : {layers["polygon_layer.shp"], 15, QgsGeometryCheck::ChangePart, QgsGeometryCheck::ChangeAdded, QgsVertexId( 0 )}
254 : : } ) );
255 : 1 : testContext.second[layers["polygon_layer.shp"]]->getFeature( 15, f );
256 : 1 : QVERIFY( f.geometry().area() > area15 );
257 : 1 : valid = testContext.second[errs2[0]->layerId()]->getFeature( errs2[0]->featureId(), f );
258 : 1 : QVERIFY( !valid );
259 : :
260 : 1 : testContext.second[layers["polygon_layer.shp"]]->getFeature( 18, f );
261 : 1 : double area18 = f.geometry().area();
262 : 3 : QVERIFY( fixCheckError( testContext.second, errs3[0],
263 : : QgsGeometryAreaCheck::MergeLongestEdge, QgsGeometryCheckError::StatusFixed,
264 : : {
265 : : {errs3[0]->layerId(), errs3[0]->featureId(), QgsGeometryCheck::ChangeFeature, QgsGeometryCheck::ChangeRemoved, QgsVertexId()},
266 : : {layers["polygon_layer.shp"], 18, QgsGeometryCheck::ChangePart, QgsGeometryCheck::ChangeRemoved, QgsVertexId( 0 )},
267 : : {layers["polygon_layer.shp"], 18, QgsGeometryCheck::ChangePart, QgsGeometryCheck::ChangeAdded, QgsVertexId( 0 )}
268 : : } ) );
269 : 1 : testContext.second[layers["polygon_layer.shp"]]->getFeature( 18, f );
270 : 1 : QVERIFY( f.geometry().area() > area18 );
271 : 1 : valid = testContext.second[errs3[0]->layerId()]->getFeature( errs3[0]->featureId(), f );
272 : 1 : QVERIFY( !valid );
273 : :
274 : 1 : testContext.second[layers["polygon_layer.shp"]]->getFeature( 21, f );
275 : 1 : double area21 = f.geometry().area();
276 : 1 : QMap<QString, int> mergeIdx;
277 : 1 : mergeIdx.insert( layers["polygon_layer.shp"], 1 ); // 1: attribute "attr"
278 : 3 : QVERIFY( fixCheckError( testContext.second, errs4[0],
279 : : QgsGeometryAreaCheck::MergeIdenticalAttribute, QgsGeometryCheckError::StatusFixed,
280 : : {
281 : : {errs4[0]->layerId(), errs4[0]->featureId(), QgsGeometryCheck::ChangeFeature, QgsGeometryCheck::ChangeRemoved, QgsVertexId()},
282 : : {layers["polygon_layer.shp"], 21, QgsGeometryCheck::ChangePart, QgsGeometryCheck::ChangeRemoved, QgsVertexId( 0 )},
283 : : {layers["polygon_layer.shp"], 21, QgsGeometryCheck::ChangePart, QgsGeometryCheck::ChangeAdded, QgsVertexId( 0 )}
284 : : }, mergeIdx ) );
285 : 1 : testContext.second[layers["polygon_layer.shp"]]->getFeature( 21, f );
286 : 1 : QVERIFY( f.geometry().area() > area21 );
287 : 1 : valid = testContext.second[errs4[0]->layerId()]->getFeature( errs4[0]->featureId(), f );
288 : 1 : QVERIFY( !valid );
289 : :
290 : 1 : cleanupTestContext( testContext );
291 : 1 : }
292 : :
293 : 1 : void TestQgsGeometryChecks::testContainedCheck()
294 : : {
295 : 1 : QTemporaryDir dir;
296 : 1 : QMap<QString, QString> layers;
297 : 1 : layers.insert( "point_layer.shp", "" );
298 : 1 : layers.insert( "line_layer.shp", "" );
299 : 1 : layers.insert( "polygon_layer.shp", "" );
300 : 1 : auto testContext = createTestContext( dir, layers );
301 : :
302 : : // Test detection
303 : 1 : QList<QgsGeometryCheckError *> checkErrors;
304 : 1 : QStringList messages;
305 : :
306 : 1 : QgsGeometryContainedCheck check( testContext.first, QVariantMap() );
307 : 1 : QgsFeedback feedback;
308 : 1 : check.collectErrors( testContext.second, checkErrors, messages, &feedback );
309 : 1 : listErrors( checkErrors, messages );
310 : :
311 : 1 : QList<QgsGeometryCheckError *> errs1;
312 : :
313 : 1 : QCOMPARE( checkErrors.size(), 4 );
314 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["point_layer.shp"], 4, QgsPointXY(), QgsVertexId(), QVariant( "polygon_layer.shp:5" ) ).size() == 1 );
315 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["point_layer.shp"], 5, QgsPointXY(), QgsVertexId(), QVariant( "polygon_layer.shp:0" ) ).size() == 1 );
316 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"], 3, QgsPointXY(), QgsVertexId(), QVariant( "polygon_layer.shp:0" ) ).size() == 1 );
317 : 1 : QVERIFY( ( errs1 = searchCheckErrors( checkErrors, layers["polygon_layer.shp"], 3, QgsPointXY(), QgsVertexId(), QVariant( "polygon_layer.shp:0" ) ) ).size() == 1 );
318 : 1 : QVERIFY( messages.contains( "Contained check failed for (polygon_layer.shp:1): the geometry is invalid" ) );
319 : :
320 : : // Test fixes
321 : 1 : QVERIFY( fixCheckError( testContext.second, errs1[0],
322 : : QgsGeometryContainedCheck::Delete, QgsGeometryCheckError::StatusFixed,
323 : : {{errs1[0]->layerId(), errs1[0]->featureId(), QgsGeometryCheck::ChangeFeature, QgsGeometryCheck::ChangeRemoved, QgsVertexId()}} ) );
324 : 1 : QgsFeature f;
325 : 1 : bool valid = testContext.second[errs1[0]->layerId()]->getFeature( errs1[0]->featureId(), f );
326 : 1 : QVERIFY( !valid );
327 : :
328 : 1 : cleanupTestContext( testContext );
329 : 1 : }
330 : :
331 : 1 : void TestQgsGeometryChecks::testDangleCheck()
332 : : {
333 : 1 : QTemporaryDir dir;
334 : 1 : QMap<QString, QString> layers;
335 : 1 : layers.insert( "point_layer.shp", "" );
336 : 1 : layers.insert( "line_layer.shp", "" );
337 : 1 : layers.insert( "polygon_layer.shp", "" );
338 : 1 : auto testContext = createTestContext( dir, layers );
339 : :
340 : : // Test detection
341 : 1 : QList<QgsGeometryCheckError *> checkErrors;
342 : 1 : QStringList messages;
343 : :
344 : 1 : QgsGeometryDangleCheck check( testContext.first, QVariantMap() );
345 : 1 : QgsFeedback feedback;
346 : 1 : check.collectErrors( testContext.second, checkErrors, messages, &feedback );
347 : 1 : listErrors( checkErrors, messages );
348 : :
349 : 1 : QList<QgsGeometryCheckError *> errs1;
350 : :
351 : 1 : QCOMPARE( checkErrors.size(), 6 );
352 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["point_layer.shp"] ).isEmpty() );
353 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["polygon_layer.shp"] ).isEmpty() );
354 : 1 : QVERIFY( ( errs1 = searchCheckErrors( checkErrors, layers["line_layer.shp"], 0, QgsPointXY( -0.7558, 0.7648 ), QgsVertexId( 1, 0, 0 ) ) ).size() == 1 );
355 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"], 2, QgsPointXY( -0.7787, -0.2237 ), QgsVertexId( 0, 0, 0 ) ).size() == 1 );
356 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"], 3, QgsPointXY( -0.2326, 0.9537 ), QgsVertexId( 0, 0, 0 ) ).size() == 1 );
357 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"], 3, QgsPointXY( 0.0715, 0.7830 ), QgsVertexId( 0, 0, 3 ) ).size() == 1 );
358 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"], 8, QgsPointXY( -1.5974, 0.5237 ), QgsVertexId( 0, 0, 0 ) ).size() == 1 );
359 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"], 8, QgsPointXY( -0.9315, 0.3146 ), QgsVertexId( 0, 0, 5 ) ).size() == 1 );
360 : :
361 : : // Test change tracking
362 : 1 : QgsVertexId oldVidx = errs1[0]->vidx();
363 : 1 : QVERIFY( errs1[0]->handleChanges( change2changes( {errs1[0]->layerId(), errs1[0]->featureId(), QgsGeometryCheck::ChangePart, QgsGeometryCheck::ChangeRemoved, QgsVertexId( 0 )} ) ) );
364 : 1 : QVERIFY( errs1[0]->vidx().part == oldVidx.part - 1 );
365 : :
366 : 1 : cleanupTestContext( testContext );
367 : 1 : }
368 : :
369 : 1 : void TestQgsGeometryChecks::testDegeneratePolygonCheck()
370 : : {
371 : 1 : QTemporaryDir dir;
372 : 1 : QMap<QString, QString> layers;
373 : 1 : layers.insert( "point_layer.shp", "" );
374 : 1 : layers.insert( "line_layer.shp", "" );
375 : 1 : layers.insert( "polygon_layer.shp", "" );
376 : 1 : auto testContext = createTestContext( dir, layers );
377 : :
378 : : // Test detection
379 : 1 : QList<QgsGeometryCheckError *> checkErrors;
380 : 1 : QStringList messages;
381 : :
382 : 1 : QgsGeometryDegeneratePolygonCheck check( testContext.first, QVariantMap() );
383 : 1 : QgsFeedback feedback;
384 : 1 : check.collectErrors( testContext.second, checkErrors, messages, &feedback );
385 : 1 : listErrors( checkErrors, messages );
386 : :
387 : 1 : QList<QgsGeometryCheckError *> errs1;
388 : :
389 : 1 : QCOMPARE( checkErrors.size(), 1 );
390 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["point_layer.shp"] ).isEmpty() );
391 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"] ).isEmpty() );
392 : 1 : QVERIFY( ( errs1 = searchCheckErrors( checkErrors, layers["polygon_layer.shp"], 6, QgsPointXY(), QgsVertexId( 0, 0 ) ) ).size() == 1 );
393 : :
394 : : // Test fixes
395 : 1 : QVERIFY( fixCheckError( testContext.second, errs1[0],
396 : : QgsGeometryDegeneratePolygonCheck::DeleteRing, QgsGeometryCheckError::StatusFixed,
397 : : {{errs1[0]->layerId(), errs1[0]->featureId(), QgsGeometryCheck::ChangeFeature, QgsGeometryCheck::ChangeRemoved, QgsVertexId()}} ) );
398 : 1 : QgsFeature f;
399 : 1 : bool valid = testContext.second[errs1[0]->layerId()]->getFeature( errs1[0]->featureId(), f );
400 : 1 : QVERIFY( !valid );
401 : :
402 : 1 : cleanupTestContext( testContext );
403 : 1 : }
404 : :
405 : 1 : void TestQgsGeometryChecks::testDuplicateCheck()
406 : : {
407 : 1 : QTemporaryDir dir;
408 : 1 : QMap<QString, QString> layers;
409 : 1 : layers.insert( "point_layer.shp", "" );
410 : 1 : layers.insert( "line_layer.shp", "" );
411 : 1 : layers.insert( "polygon_layer.shp", "" );
412 : 1 : auto testContext = createTestContext( dir, layers );
413 : :
414 : : // Test detection
415 : 1 : QList<QgsGeometryCheckError *> checkErrors;
416 : 1 : QStringList messages;
417 : :
418 : 1 : QgsGeometryDuplicateCheck check( testContext.first, QVariantMap() );
419 : 1 : QgsFeedback feedback;
420 : 1 : check.collectErrors( testContext.second, checkErrors, messages, &feedback );
421 : 1 : listErrors( checkErrors, messages );
422 : :
423 : 1 : QList<QgsGeometryCheckError *> errs1;
424 : :
425 : 1 : QCOMPARE( checkErrors.size(), 3 );
426 : 1 : QVERIFY(
427 : : searchCheckErrors( checkErrors, layers["point_layer.shp"], 6, QgsPoint(), QgsVertexId(), QVariant( "point_layer.shp:2" ) ).size() == 1
428 : : || searchCheckErrors( checkErrors, layers["point_layer.shp"], 2, QgsPoint(), QgsVertexId(), QVariant( "point_layer.shp:6" ) ).size() == 1 );
429 : 1 : QVERIFY(
430 : : searchCheckErrors( checkErrors, layers["line_layer.shp"], 4, QgsPoint(), QgsVertexId(), QVariant( "line_layer.shp:7" ) ).size() == 1
431 : : || searchCheckErrors( checkErrors, layers["line_layer.shp"], 7, QgsPoint(), QgsVertexId(), QVariant( "line_layer.shp:4" ) ).size() == 1 );
432 : 1 : QVERIFY(
433 : : ( errs1 = searchCheckErrors( checkErrors, layers["polygon_layer.shp"], 8, QgsPoint(), QgsVertexId(), QVariant( "polygon_layer.shp:7" ) ) ).size() == 1
434 : : || ( errs1 = searchCheckErrors( checkErrors, layers["polygon_layer.shp"], 7, QgsPoint(), QgsVertexId(), QVariant( "polygon_layer.shp:8" ) ) ).size() == 1 );
435 : :
436 : : // Test fixes
437 : 1 : QgsGeometryDuplicateCheckError *dupErr = static_cast<QgsGeometryDuplicateCheckError *>( errs1[0] );
438 : 1 : QString dup1LayerId = dupErr->duplicates().firstKey();
439 : 1 : QgsFeatureId dup1Fid = dupErr->duplicates()[dup1LayerId][0];
440 : 1 : QVERIFY( fixCheckError( testContext.second, dupErr,
441 : : QgsGeometryDuplicateCheck::RemoveDuplicates, QgsGeometryCheckError::StatusFixed,
442 : : {{dup1LayerId, dup1Fid, QgsGeometryCheck::ChangeFeature, QgsGeometryCheck::ChangeRemoved, QgsVertexId()}} ) );
443 : 1 : QgsFeature f;
444 : 1 : bool valid = testContext.second[dup1LayerId]->getFeature( dup1Fid, f );
445 : 1 : QVERIFY( !valid );
446 : :
447 : 1 : cleanupTestContext( testContext );
448 : 1 : }
449 : :
450 : 1 : void TestQgsGeometryChecks::testDuplicateNodesCheck()
451 : : {
452 : 1 : QTemporaryDir dir;
453 : 1 : QMap<QString, QString> layers;
454 : 1 : layers.insert( "point_layer.shp", "" );
455 : 1 : layers.insert( "line_layer.shp", "" );
456 : 1 : layers.insert( "polygon_layer.shp", "" );
457 : 1 : auto testContext = createTestContext( dir, layers );
458 : :
459 : : // Test detection
460 : 1 : QList<QgsGeometryCheckError *> checkErrors;
461 : 1 : QStringList messages;
462 : :
463 : 1 : QgsGeometryDuplicateNodesCheck check( testContext.first, QVariantMap() );
464 : 1 : QgsFeedback feedback;
465 : 1 : check.collectErrors( testContext.second, checkErrors, messages, &feedback );
466 : 1 : listErrors( checkErrors, messages );
467 : :
468 : 1 : QList<QgsGeometryCheckError *> errs1;
469 : :
470 : 1 : QCOMPARE( checkErrors.size(), 4 );
471 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["point_layer.shp"] ).isEmpty() );
472 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"], 0, QgsPointXY( -0.6360, 0.6203 ), QgsVertexId( 0, 0, 5 ) ).size() == 1 );
473 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"], 6, QgsPointXY( 0.2473, 2.0821 ), QgsVertexId( 0, 0, 1 ) ).size() == 1 );
474 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"], 6, QgsPointXY( 0.5158, 2.0930 ), QgsVertexId( 0, 0, 3 ) ).size() == 1 );
475 : 1 : QVERIFY( ( errs1 = searchCheckErrors( checkErrors, layers["polygon_layer.shp"], 4, QgsPointXY( 1.6319, 0.5642 ), QgsVertexId( 0, 0, 1 ) ) ).size() == 1 );
476 : :
477 : : // Test fixes
478 : 1 : QgsFeature f;
479 : :
480 : 1 : testContext.second[errs1[0]->layerId()]->getFeature( errs1[0]->featureId(), f );
481 : 1 : int n1 = f.geometry().constGet()->vertexCount( errs1[0]->vidx().part, errs1[0]->vidx().ring );
482 : 1 : QVERIFY( fixCheckError( testContext.second, errs1[0],
483 : : QgsGeometryDuplicateNodesCheck::RemoveDuplicates, QgsGeometryCheckError::StatusFixed,
484 : : {{errs1[0]->layerId(), errs1[0]->featureId(), QgsGeometryCheck::ChangeNode, QgsGeometryCheck::ChangeRemoved, errs1[0]->vidx()}} ) );
485 : 1 : testContext.second[errs1[0]->layerId()]->getFeature( errs1[0]->featureId(), f );
486 : 1 : int n2 = f.geometry().constGet()->vertexCount( errs1[0]->vidx().part, errs1[0]->vidx().ring );
487 : 1 : QCOMPARE( n1, n2 + 1 );
488 : :
489 : 1 : cleanupTestContext( testContext );
490 : 1 : }
491 : :
492 : 1 : void TestQgsGeometryChecks::testFollowBoundariesCheck()
493 : : {
494 : 1 : QTemporaryDir dir;
495 : 1 : QMap<QString, QString> layers;
496 : 1 : layers.insert( "follow_ref.shp", "" );
497 : 1 : layers.insert( "follow_subj.shp", "" );
498 : 1 : auto testContext = createTestContext( dir, layers );
499 : :
500 : : // Test detection
501 : 1 : QList<QgsGeometryCheckError *> checkErrors;
502 : 1 : QStringList messages;
503 : :
504 : 1 : QgsFeedback feedback;
505 : 1 : QgsGeometryFollowBoundariesCheck( testContext.first, QVariantMap(), testContext.second[layers["follow_ref.shp"]]->layer() ).collectErrors( testContext.second, checkErrors, messages, &feedback );
506 : 1 : listErrors( checkErrors, messages );
507 : :
508 : 1 : QCOMPARE( checkErrors.size(), 2 );
509 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["follow_subj.shp"], 1 ).size() == 1 );
510 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["follow_subj.shp"], 3 ).size() == 1 );
511 : :
512 : 1 : cleanupTestContext( testContext );
513 : 1 : }
514 : :
515 : 1 : void TestQgsGeometryChecks::testGapCheck()
516 : : {
517 : 1 : QTemporaryDir dir;
518 : 1 : QMap<QString, QString> layers;
519 : 1 : layers.insert( "gap_layer.shp", "" );
520 : 1 : auto testContext = createTestContext( dir, layers );
521 : :
522 : : // Test detection
523 : 1 : QList<QgsGeometryCheckError *> checkErrors;
524 : 1 : QStringList messages;
525 : :
526 : 1 : QVariantMap configuration;
527 : 1 : configuration.insert( "gapThreshold", 0.01 );
528 : :
529 : 1 : QgsGeometryGapCheck check( testContext.first, configuration );
530 : 1 : QgsFeedback feedback;
531 : 1 : check.collectErrors( testContext.second, checkErrors, messages, &feedback );
532 : 1 : listErrors( checkErrors, messages );
533 : :
534 : 1 : QList<QgsGeometryCheckError *> errs1;
535 : :
536 : 1 : QCOMPARE( checkErrors.size(), 5 );
537 : 1 : QCOMPARE( searchCheckErrors( checkErrors, "", -1, QgsPointXY( 0.2924, -0.8798 ), QgsVertexId(), 0.0027 ).size(), 1 );
538 : 1 : QCOMPARE( searchCheckErrors( checkErrors, "", -1, QgsPointXY( 0.4238, -0.7479 ), QgsVertexId(), 0.0071 ).size(), 1 );
539 : 1 : QCOMPARE( searchCheckErrors( checkErrors, "", -1, QgsPointXY( 0.0094, -0.4448 ), QgsVertexId(), 0.0033 ).size(), 1 );
540 : 1 : errs1 = searchCheckErrors( checkErrors, "", -1, QgsPointXY( 0.2939, -0.4694 ), QgsVertexId(), 0.0053 );
541 : 1 : QCOMPARE( errs1.size(), 1 );
542 : 1 : QCOMPARE( searchCheckErrors( checkErrors, "", -1, QgsPointXY( 0.6284, -0.3641 ), QgsVertexId(), 0.0018 ).size(), 1 );
543 : :
544 : : // TestQgsGeometryChecks::testGapCheck()
545 : 1 : QgsGeometryCheckError *error = searchCheckErrors( checkErrors, "", -1, QgsPointXY( 0.2924, -0.8798 ), QgsVertexId(), 0.0027 ).first();
546 : :
547 : 1 : QCOMPARE( error->contextBoundingBox().snappedToGrid( 0.0001 ), QgsRectangle( -0.0259, -1.0198, 0.6178, -0.4481 ) );
548 : 1 : QCOMPARE( error->affectedAreaBBox().snappedToGrid( 0.0001 ), QgsRectangle( 0.246, -0.9998, 0.3939, -0.77 ) );
549 : :
550 : : // Test fixes
551 : 1 : QgsFeature f;
552 : :
553 : 1 : testContext.second[layers["gap_layer.shp"]]->getFeature( 0, f );
554 : 1 : double areaOld = f.geometry().area();
555 : 2 : QVERIFY( fixCheckError( testContext.second, errs1[0],
556 : : QgsGeometryGapCheck::MergeLongestEdge, QgsGeometryCheckError::StatusFixed,
557 : : {
558 : : {layers["gap_layer.shp"], 0, QgsGeometryCheck::ChangePart, QgsGeometryCheck::ChangeRemoved, QgsVertexId( 0 )},
559 : : {layers["gap_layer.shp"], 0, QgsGeometryCheck::ChangePart, QgsGeometryCheck::ChangeAdded, QgsVertexId( 0 )}
560 : : } ) );
561 : 1 : testContext.second[layers["gap_layer.shp"]]->getFeature( 0, f );
562 : 1 : QVERIFY( f.geometry().area() > areaOld );
563 : :
564 : 1 : cleanupTestContext( testContext );
565 : 1 : }
566 : :
567 : 1 : void TestQgsGeometryChecks::testAllowedGaps()
568 : : {
569 : 1 : QTemporaryDir dir;
570 : 1 : QMap<QString, QString> layers;
571 : 1 : layers.insert( "gap_layer.shp", "" );
572 : 1 : auto testContext = createTestContext( dir, layers );
573 : :
574 : : // Allowed gaps layer
575 : 4 : std::unique_ptr<QgsVectorLayer> allowedGaps = std::make_unique< QgsVectorLayer >( QStringLiteral( "Polygon?crs=epsg:4326" ), QStringLiteral( "allowedGaps" ), QStringLiteral( "memory" ) );
576 : 1 : QgsProject::instance()->addMapLayer( allowedGaps.get(), true, false );
577 : :
578 : : // Test detection
579 : 1 : QList<QgsGeometryCheckError *> checkErrors;
580 : 1 : QStringList messages;
581 : :
582 : 1 : QVariantMap configuration;
583 : 1 : configuration.insert( "gapThreshold", 0.01 );
584 : 2 : configuration.insert( QStringLiteral( "allowedGapsEnabled" ), true );
585 : 2 : configuration.insert( QStringLiteral( "allowedGapsLayer" ), allowedGaps->id() );
586 : 2 : configuration.insert( QStringLiteral( "allowedGapsBuffer" ), 0.01 );
587 : :
588 : 1 : QgsGeometryGapCheck check( testContext.first, configuration );
589 : 1 : check.prepare( testContext.first, configuration );
590 : :
591 : 1 : QgsFeedback feedback;
592 : 1 : check.collectErrors( testContext.second, checkErrors, messages, &feedback );
593 : 1 : listErrors( checkErrors, messages );
594 : :
595 : 1 : QCOMPARE( checkErrors.size(), 5 );
596 : :
597 : : // TestQgsGeometryChecks::testGapCheck()
598 : 1 : QgsGeometryCheckError *error = searchCheckErrors( checkErrors, "", -1, QgsPointXY( 0.2924, -0.8798 ), QgsVertexId(), 0.0027 ).first();
599 : :
600 : 1 : QgsGeometryCheck::Changes changes;
601 : 1 : check.fixError( testContext.second, error, 2, QMap<QString, int>(), changes );
602 : :
603 : 1 : QgsFeature f;
604 : 1 : QgsFeatureIterator it = allowedGaps->getFeatures();
605 : 1 : QVERIFY( it.nextFeature( f ) );
606 : :
607 : 1 : qDebug() << GEOSversion() << "\n";
608 : : if ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR < 9 )
609 : : {
610 : : QCOMPARE( f.geometry().asWkt( 4 ), QgsGeometry::fromWkt( "Polygon ((0.393901 -0.769953, 0.25997 -0.88388, 0.26997 -0.99981, 0.24598 -0.865897, 0.3939 -0.76995))" ).asWkt( 4 ) );
611 : : }
612 : : else
613 : : {
614 : 1 : QCOMPARE( f.geometry().asWkt( 4 ), QgsGeometry::fromWkt( "Polygon ((0.246 -0.8659, 0.3939 -0.77, 0.26 -0.8839, 0.27 -0.9998, 0.246 -0.8659))" ).asWkt( 4 ) );
615 : : }
616 : :
617 : : // Run check again after adding the gap geometry to the allowed gaps layer: one less error
618 : 1 : check.prepare( testContext.first, configuration );
619 : 1 : qDeleteAll( checkErrors );
620 : 1 : checkErrors.clear();
621 : 1 : check.collectErrors( testContext.second, checkErrors, messages, &feedback );
622 : 1 : listErrors( checkErrors, messages );
623 : 1 : QCOMPARE( checkErrors.size(), 4 );
624 : :
625 : : // Make the gap geometry a bit smaller (0.0001), but still in the buffer tolerance of 0.01 specified in the check configuration
626 : : // Watch out: buffering with only 0.001 sounds like a good idea on first sight but fails at the corners
627 : 1 : allowedGaps->startEditing();
628 : 1 : QgsGeometry g = f.geometry();
629 : 1 : g = g.buffer( -0.0001, 10 );
630 : 1 : f.setGeometry( g );
631 : 1 : allowedGaps->updateFeature( f );
632 : :
633 : : // Still, this gap should not be reported
634 : 1 : check.prepare( testContext.first, configuration );
635 : 1 : qDeleteAll( checkErrors );
636 : 1 : checkErrors.clear();
637 : 1 : check.collectErrors( testContext.second, checkErrors, messages, &feedback );
638 : 1 : listErrors( checkErrors, messages );
639 : 1 : QCOMPARE( checkErrors.size(), 4 );
640 : :
641 : 1 : cleanupTestContext( testContext );
642 : 1 : }
643 : :
644 : 1 : void TestQgsGeometryChecks::testMissingVertexCheck()
645 : : {
646 : 1 : QTemporaryDir dir;
647 : 1 : QMap<QString, QString> layers;
648 : 2 : layers.insert( QStringLiteral( "missing_vertex.gpkg" ), QString() );
649 : 1 : auto testContext = createTestContext( dir, layers );
650 : :
651 : : // Test detection
652 : 1 : QList<QgsGeometryCheckError *> checkErrors;
653 : 1 : QStringList messages;
654 : :
655 : 1 : QgsGeometryMissingVertexCheck check( testContext.first, QVariantMap() );
656 : 1 : QgsFeedback feedback;
657 : 1 : check.collectErrors( testContext.second, checkErrors, messages, &feedback );
658 : 1 : listErrors( checkErrors, messages );
659 : :
660 : 1 : const QString layerId = testContext.second.first()->layerId();
661 : 1 : QVERIFY( searchCheckErrors( checkErrors, layerId, 0, QgsPointXY( 0.251153, -0.460895 ), QgsVertexId() ).size() == 1 );
662 : 1 : QVERIFY( searchCheckErrors( checkErrors, layerId, 3, QgsPointXY( 0.257985, -0.932886 ), QgsVertexId() ).size() == 1 );
663 : 1 : QVERIFY( searchCheckErrors( checkErrors, layerId, 5, QgsPointXY( 0.59781, -0.480033 ), QgsVertexId() ).size() == 1 );
664 : 1 : QVERIFY( searchCheckErrors( checkErrors, layerId, 5, QgsPointXY( 0.605252, -0.664875 ), QgsVertexId() ).size() == 1 );
665 : 1 : QVERIFY( searchCheckErrors( checkErrors, layerId, 4, QgsPointXY( 0.259197, -0.478311 ), QgsVertexId() ).size() == 1 );
666 : :
667 : 1 : QCOMPARE( checkErrors.size(), 5 );
668 : :
669 : 1 : cleanupTestContext( testContext );
670 : 1 : }
671 : :
672 : 1 : void TestQgsGeometryChecks::testHoleCheck()
673 : : {
674 : 1 : QTemporaryDir dir;
675 : 1 : QMap<QString, QString> layers;
676 : 1 : layers.insert( "point_layer.shp", "" );
677 : 1 : layers.insert( "line_layer.shp", "" );
678 : 1 : layers.insert( "polygon_layer.shp", "" );
679 : 1 : auto testContext = createTestContext( dir, layers );
680 : :
681 : : // Test detection
682 : 1 : QList<QgsGeometryCheckError *> checkErrors;
683 : 1 : QStringList messages;
684 : :
685 : 1 : QgsGeometryHoleCheck check( testContext.first, QVariantMap() );
686 : 1 : QgsFeedback feedback;
687 : 1 : check.collectErrors( testContext.second, checkErrors, messages, &feedback );
688 : 1 : listErrors( checkErrors, messages );
689 : :
690 : 1 : QList<QgsGeometryCheckError *> errs1;
691 : :
692 : 1 : QCOMPARE( checkErrors.size(), 1 );
693 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["point_layer.shp"] ).isEmpty() );
694 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"] ).isEmpty() );
695 : 1 : QVERIFY( ( errs1 = searchCheckErrors( checkErrors, layers["polygon_layer.shp"], 12, QgsPointXY( 0.1902, 0.0951 ), QgsVertexId( 0, 1 ) ) ).size() == 1 );
696 : :
697 : : // Test fixes
698 : 1 : QgsFeature f;
699 : :
700 : 1 : QVERIFY( fixCheckError( testContext.second, errs1[0],
701 : : QgsGeometryHoleCheck::RemoveHoles, QgsGeometryCheckError::StatusFixed,
702 : : {
703 : : {errs1[0]->layerId(), errs1[0]->featureId(), QgsGeometryCheck::ChangeRing, QgsGeometryCheck::ChangeRemoved, QgsVertexId( 0, 1 )}
704 : : } ) );
705 : 1 : testContext.second[errs1[0]->layerId()]->getFeature( errs1[0]->featureId(), f );
706 : 1 : QVERIFY( f.geometry().constGet()->ringCount( 0 ) == 1 );
707 : :
708 : 1 : cleanupTestContext( testContext );
709 : 1 : }
710 : :
711 : 1 : void TestQgsGeometryChecks::testLineIntersectionCheck()
712 : : {
713 : 1 : QTemporaryDir dir;
714 : 1 : QMap<QString, QString> layers;
715 : 1 : layers.insert( "point_layer.shp", "" );
716 : 1 : layers.insert( "line_layer.shp", "" );
717 : 1 : layers.insert( "polygon_layer.shp", "" );
718 : 1 : auto testContext = createTestContext( dir, layers );
719 : :
720 : : // Test detection
721 : 1 : QList<QgsGeometryCheckError *> checkErrors;
722 : 1 : QStringList messages;
723 : :
724 : 1 : QgsGeometryLineIntersectionCheck check( testContext.first, QVariantMap() );
725 : 1 : QgsFeedback feedback;
726 : 1 : check.collectErrors( testContext.second, checkErrors, messages, &feedback );
727 : 1 : listErrors( checkErrors, messages );
728 : :
729 : 1 : QCOMPARE( checkErrors.size(), 1 );
730 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["point_layer.shp"] ).isEmpty() );
731 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["polygon_layer.shp"] ).isEmpty() );
732 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"], 1, QgsPointXY( -0.5594, 0.4098 ), QgsVertexId( 0 ), QVariant( "line_layer.shp:0" ) ).size() == 1 );
733 : :
734 : 1 : cleanupTestContext( testContext );
735 : 1 : }
736 : :
737 : 1 : void TestQgsGeometryChecks::testLineLayerIntersectionCheck()
738 : : {
739 : 1 : QTemporaryDir dir;
740 : 1 : QMap<QString, QString> layers;
741 : 1 : layers.insert( "point_layer.shp", "" );
742 : 1 : layers.insert( "line_layer.shp", "" );
743 : 1 : layers.insert( "polygon_layer.shp", "" );
744 : 1 : auto testContext = createTestContext( dir, layers );
745 : :
746 : : // Test detection
747 : 1 : QList<QgsGeometryCheckError *> checkErrors;
748 : 1 : QStringList messages;
749 : :
750 : 1 : QVariantMap configuration;
751 : 1 : configuration.insert( "checkLayer", layers["polygon_layer.shp"] );
752 : :
753 : 1 : QgsGeometryLineLayerIntersectionCheck check( testContext.first, configuration );
754 : 1 : QgsFeedback feedback;
755 : 1 : check.collectErrors( testContext.second, checkErrors, messages, &feedback );
756 : 1 : listErrors( checkErrors, messages );
757 : :
758 : 1 : QCOMPARE( checkErrors.size(), 5 );
759 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["point_layer.shp"] ).isEmpty() );
760 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["polygon_layer.shp"] ).isEmpty() );
761 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"], 3, QgsPointXY( -0.1890, 0.9043 ), QgsVertexId( 0 ), QVariant( "polygon_layer.shp:3" ) ).size() == 1 );
762 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"], 4, QgsPointXY( 0.9906, 1.1169 ), QgsVertexId( 0 ), QVariant( "polygon_layer.shp:2" ) ).size() == 1 );
763 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"], 4, QgsPointXY( 1.0133, 1.0772 ), QgsVertexId( 0 ), QVariant( "polygon_layer.shp:2" ) ).size() == 1 );
764 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"], 7, QgsPointXY( 0.9906, 1.1169 ), QgsVertexId( 0 ), QVariant( "polygon_layer.shp:2" ) ).size() == 1 );
765 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"], 7, QgsPointXY( 1.0133, 1.0772 ), QgsVertexId( 0 ), QVariant( "polygon_layer.shp:2" ) ).size() == 1 );
766 : :
767 : 1 : cleanupTestContext( testContext );
768 : 1 : }
769 : :
770 : 1 : void TestQgsGeometryChecks::testMultipartCheck()
771 : : {
772 : 1 : QTemporaryDir dir;
773 : 1 : QMap<QString, QString> layers;
774 : 1 : layers.insert( "point_layer.shp", "" );
775 : 1 : layers.insert( "line_layer.shp", "" );
776 : 1 : layers.insert( "polygon_layer.shp", "" );
777 : 1 : auto testContext = createTestContext( dir, layers );
778 : :
779 : : // Test detection
780 : 1 : QList<QgsGeometryCheckError *> checkErrors;
781 : 1 : QStringList messages;
782 : :
783 : 1 : QgsGeometryMultipartCheck check( testContext.first, QVariantMap() );
784 : 1 : QgsFeedback feedback;
785 : 1 : check.collectErrors( testContext.second, checkErrors, messages, &feedback );
786 : 1 : listErrors( checkErrors, messages );
787 : :
788 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["point_layer.shp"] ).isEmpty() );
789 : : // Easier to ensure that multipart features don't appear as errors than verifying each single-part multi-type feature
790 : 1 : QVERIFY( QgsWkbTypes::isSingleType( testContext.second[layers["point_layer.shp"]]->layer()->wkbType() ) );
791 : 1 : QVERIFY( QgsWkbTypes::isMultiType( testContext.second[layers["line_layer.shp"]]->layer()->wkbType() ) );
792 : 1 : QVERIFY( QgsWkbTypes::isMultiType( testContext.second[layers["polygon_layer.shp"]]->layer()->wkbType() ) );
793 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"] ).size() > 0 );
794 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["polygon_layer.shp"] ).size() > 0 );
795 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"], 0 ).isEmpty() );
796 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["polygon_layer.shp"], 1 ).isEmpty() );
797 : :
798 : : // Two specific errors that will be fixed
799 : 1 : QList<QgsGeometryCheckError *> errs1;
800 : 1 : QList<QgsGeometryCheckError *> errs2;
801 : :
802 : 1 : QVERIFY( ( errs1 = searchCheckErrors( checkErrors, layers["line_layer.shp"], 2, QgsPointXY( -0.6642, -0.2422 ) ) ).size() == 1 );
803 : 1 : QVERIFY( ( errs2 = searchCheckErrors( checkErrors, layers["polygon_layer.shp"], 13, QgsPointXY( 0.5026, 3.0267 ) ) ).size() == 1 );
804 : :
805 : : // Test fixes
806 : 1 : QgsFeature f;
807 : : #if 0
808 : : // The ogr provider apparently automatically re-converts the geometry type to a multitype...
809 : : QVERIFY( fixCheckError( testContext.second, errs1[0],
810 : : QgsGeometryMultipartCheck::ConvertToSingle, QgsGeometryCheckError::StatusFixed,
811 : : {
812 : : {errs1[0]->layerId(), errs1[0]->featureId(), QgsGeometryCheck::ChangeFeature, QgsGeometryCheck::ChangeChanged, QgsVertexId( )}
813 : : } ) );
814 : : testContext.second[errs1[0]->layerId()]->get( errs1[0]->featureId(), f );
815 : : QVERIFY( QgsWkbTypes::isSingleType( f.geometry().geometry()->wkbType() ) );
816 : : #endif
817 : :
818 : 1 : QVERIFY( fixCheckError( testContext.second, errs2[0],
819 : : QgsGeometryMultipartCheck::RemoveObject, QgsGeometryCheckError::StatusFixed,
820 : : {
821 : : {errs2[0]->layerId(), errs2[0]->featureId(), QgsGeometryCheck::ChangeFeature, QgsGeometryCheck::ChangeRemoved, QgsVertexId( )}
822 : : } ) );
823 : 1 : bool valid = testContext.second[errs2[0]->layerId()]->getFeature( errs2[0]->featureId(), f );
824 : 1 : QVERIFY( !valid );
825 : :
826 : 1 : cleanupTestContext( testContext );
827 : 1 : }
828 : :
829 : 1 : void TestQgsGeometryChecks::testOverlapCheck()
830 : : {
831 : 1 : QTemporaryDir dir;
832 : 1 : QMap<QString, QString> layers;
833 : 1 : layers.insert( "point_layer.shp", "" );
834 : 1 : layers.insert( "line_layer.shp", "" );
835 : 1 : layers.insert( "polygon_layer.shp", "" );
836 : :
837 : 1 : auto testContext = createTestContext( dir, layers );
838 : :
839 : : // Test detection
840 : 1 : QList<QgsGeometryCheckError *> checkErrors;
841 : 1 : QStringList messages;
842 : :
843 : 1 : QVariantMap configuration;
844 : 1 : configuration.insert( "maxOverlapArea", 0.01 );
845 : :
846 : 1 : QgsGeometryOverlapCheck check( testContext.first, configuration );
847 : 1 : QgsFeedback feedback;
848 : 1 : check.collectErrors( testContext.second, checkErrors, messages, &feedback );
849 : 1 : listErrors( checkErrors, messages );
850 : :
851 : 1 : QList<QgsGeometryCheckError *> errs1;
852 : :
853 : 1 : QCOMPARE( checkErrors.size(), 2 );
854 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["point_layer.shp"] ).isEmpty() );
855 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"] ).isEmpty() );
856 : 1 : QVERIFY( ( errs1 = searchCheckErrors( checkErrors, layers["polygon_layer.shp"], 10 ) ).size() == 2 );
857 : 1 : QVERIFY( messages.contains( "Overlap check failed for (polygon_layer.shp:1): the geometry is invalid" ) );
858 : :
859 : : // Test fixes
860 : 1 : QgsFeature f;
861 : :
862 : 1 : testContext.second[errs1[0]->layerId()]->getFeature( errs1[0]->featureId(), f );
863 : 1 : double areaOld = f.geometry().area();
864 : 1 : QVERIFY( fixCheckError( testContext.second, errs1[0],
865 : : QgsGeometryOverlapCheck::Subtract, QgsGeometryCheckError::StatusFixed,
866 : : {
867 : : {errs1[0]->layerId(), errs1[0]->featureId(), QgsGeometryCheck::ChangeFeature, QgsGeometryCheck::ChangeChanged, QgsVertexId( )}
868 : : } ) );
869 : 1 : testContext.second[errs1[0]->layerId()]->getFeature( errs1[0]->featureId(), f );
870 : 1 : QVERIFY( f.geometry().area() < areaOld );
871 : :
872 : 1 : cleanupTestContext( testContext );
873 : 1 : }
874 : :
875 : 1 : void TestQgsGeometryChecks::testOverlapCheckNoMaxArea()
876 : : {
877 : 1 : QTemporaryDir dir;
878 : 1 : QMap<QString, QString> layers;
879 : 2 : layers.insert( QStringLiteral( "point_layer.shp" ), QString() );
880 : 2 : layers.insert( QStringLiteral( "line_layer.shp" ), QString() );
881 : 2 : layers.insert( QStringLiteral( "polygon_layer.shp" ), QString() );
882 : :
883 : 1 : auto testContext = createTestContext( dir, layers );
884 : :
885 : : // Test detection
886 : 1 : QList<QgsGeometryCheckError *> checkErrors;
887 : 1 : QStringList messages;
888 : :
889 : 1 : QVariantMap configuration;
890 : 1 : configuration.insert( "maxOverlapArea", 0.0 );
891 : :
892 : 1 : QgsGeometryOverlapCheck check( testContext.first, configuration );
893 : 1 : QgsFeedback feedback;
894 : 1 : check.collectErrors( testContext.second, checkErrors, messages, &feedback );
895 : 1 : listErrors( checkErrors, messages );
896 : :
897 : 1 : QList<QgsGeometryCheckError *> errs1;
898 : 1 : QCOMPARE( checkErrors.size(), 4 );
899 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["point_layer.shp"] ).isEmpty() );
900 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"] ).isEmpty() );
901 : 1 : errs1 = searchCheckErrors( checkErrors, layers["polygon_layer.shp"], 10 );
902 : 1 : QCOMPARE( errs1.size(), 2 );
903 : 1 : errs1 = searchCheckErrors( checkErrors, layers["polygon_layer.shp"], 9 );
904 : 1 : QCOMPARE( errs1.size(), 2 );
905 : 1 : }
906 : :
907 : 1 : void TestQgsGeometryChecks::testPointCoveredByLineCheck()
908 : : {
909 : 1 : QTemporaryDir dir;
910 : 1 : QMap<QString, QString> layers;
911 : 1 : layers.insert( "point_layer.shp", "" );
912 : 1 : layers.insert( "line_layer.shp", "" );
913 : 1 : layers.insert( "polygon_layer.shp", "" );
914 : 1 : auto testContext = createTestContext( dir, layers );
915 : :
916 : : // Test detection
917 : 1 : QList<QgsGeometryCheckError *> checkErrors;
918 : 1 : QStringList messages;
919 : :
920 : 1 : QgsGeometryPointCoveredByLineCheck errs( testContext.first, QVariantMap() );
921 : 1 : QgsFeedback feedback;
922 : 1 : errs.collectErrors( testContext.second, checkErrors, messages, &feedback );
923 : 1 : listErrors( checkErrors, messages );
924 : :
925 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"] ).isEmpty() );
926 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["polygon_layer.shp"] ).isEmpty() );
927 : : // Easier to test that no points which are covered by a line are marked as errors
928 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["point_layer.shp"], 0 ).isEmpty() );
929 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["point_layer.shp"], 1 ).isEmpty() );
930 : :
931 : 1 : cleanupTestContext( testContext );
932 : 1 : }
933 : :
934 : 1 : void TestQgsGeometryChecks::testPointInPolygonCheck()
935 : : {
936 : 1 : QTemporaryDir dir;
937 : 1 : QMap<QString, QString> layers;
938 : 1 : layers.insert( "point_layer.shp", "" );
939 : 1 : layers.insert( "line_layer.shp", "" );
940 : 1 : layers.insert( "polygon_layer.shp", "" );
941 : 1 : auto testContext = createTestContext( dir, layers );
942 : :
943 : : // Test detection
944 : 1 : QList<QgsGeometryCheckError *> checkErrors;
945 : 1 : QStringList messages;
946 : :
947 : 1 : QgsGeometryPointInPolygonCheck check( testContext.first, QVariantMap() );
948 : 1 : QgsFeedback feedback;
949 : 1 : check.collectErrors( testContext.second, checkErrors, messages, &feedback );
950 : 1 : listErrors( checkErrors, messages );
951 : :
952 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"] ).isEmpty() );
953 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["polygon_layer.shp"] ).isEmpty() );
954 : : // Check that the point which is properly inside a polygon is not listed as error
955 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["point_layer.shp"], 5 ).isEmpty() );
956 : 1 : QVERIFY( messages.contains( "Point in polygon check failed for (polygon_layer.shp:1): the geometry is invalid" ) );
957 : :
958 : 1 : cleanupTestContext( testContext );
959 : 1 : }
960 : :
961 : 1 : void TestQgsGeometryChecks::testSegmentLengthCheck()
962 : : {
963 : 1 : QTemporaryDir dir;
964 : 1 : QMap<QString, QString> layers;
965 : 1 : layers.insert( "point_layer.shp", "" );
966 : 1 : layers.insert( "line_layer.shp", "" );
967 : 1 : layers.insert( "polygon_layer.shp", "" );
968 : 1 : auto testContext = createTestContext( dir, layers );
969 : :
970 : : // Test detection
971 : 1 : QList<QgsGeometryCheckError *> checkErrors;
972 : 1 : QStringList messages;
973 : :
974 : 1 : QVariantMap configuration;
975 : 1 : configuration.insert( "minSegmentLength", 0.03 );
976 : :
977 : 1 : QgsGeometrySegmentLengthCheck check( testContext.first, configuration );
978 : 1 : QgsFeedback feedback;
979 : 1 : check.collectErrors( testContext.second, checkErrors, messages, &feedback );
980 : 1 : listErrors( checkErrors, messages );
981 : :
982 : 1 : QCOMPARE( checkErrors.size(), 4 );
983 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["point_layer.shp"] ).isEmpty() );
984 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"], 3, QgsPointXY( 0.0753, 0.7921 ), QgsVertexId( 0, 0, 3 ), 0.0197 ).size() == 1 );
985 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["polygon_layer.shp"], 2, QgsPointXY( 0.7807, 1.1009 ), QgsVertexId( 0, 0, 0 ), 0.0176 ).size() == 1 );
986 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["polygon_layer.shp"], 10, QgsPointXY( -0.2819, 1.3553 ), QgsVertexId( 0, 0, 2 ), 0.0281 ).size() == 1 );
987 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["polygon_layer.shp"], 11, QgsPointXY( -0.2819, 1.3553 ), QgsVertexId( 0, 0, 0 ), 0.0281 ).size() == 1 );
988 : :
989 : 1 : cleanupTestContext( testContext );
990 : 1 : }
991 : :
992 : 1 : void TestQgsGeometryChecks::testSelfContactCheck()
993 : : {
994 : 1 : QTemporaryDir dir;
995 : 1 : QMap<QString, QString> layers;
996 : 1 : layers.insert( "point_layer.shp", "" );
997 : 1 : layers.insert( "line_layer.shp", "" );
998 : 1 : layers.insert( "polygon_layer.shp", "" );
999 : 1 : auto testContext = createTestContext( dir, layers );
1000 : :
1001 : : // Test detection
1002 : 1 : QList<QgsGeometryCheckError *> checkErrors;
1003 : 1 : QStringList messages;
1004 : :
1005 : 1 : QgsGeometrySelfContactCheck check( testContext.first, QVariantMap() );
1006 : 1 : QgsFeedback feedback;
1007 : 1 : check.collectErrors( testContext.second, checkErrors, messages, &feedback );
1008 : 1 : listErrors( checkErrors, messages );
1009 : :
1010 : 1 : QCOMPARE( checkErrors.size(), 3 );
1011 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["point_layer.shp"] ).isEmpty() );
1012 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"], 5, QgsPointXY( -1.2280, -0.8654 ), QgsVertexId( 0, 0, 0 ) ).size() == 1 );
1013 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"], 5, QgsPointXY( -1.2399, -1.0502 ), QgsVertexId( 0, 0, 6 ) ).size() == 1 );
1014 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["polygon_layer.shp"], 9, QgsPointXY( -0.2080, 1.9830 ), QgsVertexId( 0, 0, 3 ) ).size() == 1 );
1015 : :
1016 : :
1017 : 1 : cleanupTestContext( testContext );
1018 : :
1019 : : // https://github.com/qgis/QGIS/issues/28228
1020 : : // test with a linestring which collapses to an empty linestring
1021 : 2 : QgsGeometryCheckContext context( 1, QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3857" ) ), QgsCoordinateTransformContext(), nullptr );
1022 : 1 : QgsGeometrySelfContactCheck check2( &context, QVariantMap() );
1023 : 2 : QgsGeometry g = QgsGeometry::fromWkt( QStringLiteral( "MultiLineString ((2988987 10262483, 2988983 10262480, 2988991 10262432, 2988990 10262419, 2988977 10262419, 2988976 10262420, 2988967 10262406, 2988970 10262421, 2988971 10262424),(2995620 10301368))" ) );
1024 : 1 : QList<QgsSingleGeometryCheckError *> errors = check2.processGeometry( g );
1025 : 1 : QVERIFY( errors.isEmpty() );
1026 : :
1027 : : // test with totally empty line
1028 : 1 : qgsgeometry_cast< QgsMultiLineString * >( g.get() )->addGeometry( new QgsLineString() );
1029 : 1 : errors = check2.processGeometry( g );
1030 : 1 : QVERIFY( errors.isEmpty() );
1031 : 1 : }
1032 : :
1033 : 1 : void TestQgsGeometryChecks::testSelfIntersectionCheck()
1034 : : {
1035 : 1 : QTemporaryDir dir;
1036 : 1 : QMap<QString, QString> layers;
1037 : 1 : layers.insert( "point_layer.shp", "" );
1038 : 1 : layers.insert( "line_layer.shp", "" );
1039 : 1 : layers.insert( "polygon_layer.shp", "" );
1040 : 1 : auto testContext = createTestContext( dir, layers );
1041 : :
1042 : : // Test detection
1043 : 1 : QList<QgsGeometryCheckError *> checkErrors;
1044 : 1 : QStringList messages;
1045 : :
1046 : 1 : QgsGeometrySelfIntersectionCheck check( testContext.first, QVariantMap() );
1047 : 1 : QgsFeedback feedback;
1048 : 1 : check.collectErrors( testContext.second, checkErrors, messages, &feedback );
1049 : 1 : listErrors( checkErrors, messages );
1050 : :
1051 : 1 : QList<QgsGeometryCheckError *> errs1;
1052 : 1 : QList<QgsGeometryCheckError *> errs2;
1053 : 1 : QList<QgsGeometryCheckError *> errs3;
1054 : 1 : QList<QgsGeometryCheckError *> errs4;
1055 : :
1056 : 1 : QCOMPARE( checkErrors.size(), 5 );
1057 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["point_layer.shp"] ).isEmpty() );
1058 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"], 1, QgsPointXY( -0.1199, 0.1440 ), QgsVertexId( 0, 0 ) ).size() == 1 );
1059 : 1 : QVERIFY( ( errs1 = searchCheckErrors( checkErrors, layers["line_layer.shp"], 1, QgsPointXY( -0.1997, 0.1044 ), QgsVertexId( 0, 0 ) ) ).size() == 1 );
1060 : 1 : QVERIFY( ( errs2 = searchCheckErrors( checkErrors, layers["line_layer.shp"], 8, QgsPointXY( -1.1985, 0.3128 ), QgsVertexId( 0, 0 ) ) ).size() == 1 );
1061 : 1 : QVERIFY( ( errs3 = searchCheckErrors( checkErrors, layers["polygon_layer.shp"], 1, QgsPointXY( 1.2592, 0.0888 ), QgsVertexId( 0, 0 ) ) ).size() == 1 );
1062 : 1 : QVERIFY( ( errs4 = searchCheckErrors( checkErrors, layers["polygon_layer.shp"], 12, QgsPointXY( 0.2213, 0.2365 ), QgsVertexId( 0, 0 ) ) ).size() == 1 );
1063 : :
1064 : : // Test fixes
1065 : 1 : QgsFeature f;
1066 : :
1067 : 1 : int nextId = testContext.second[errs1[0]->layerId()]->layer()->featureCount();
1068 : 3 : QVERIFY( fixCheckError( testContext.second, errs1[0],
1069 : : QgsGeometrySelfIntersectionCheck::ToSingleObjects, QgsGeometryCheckError::StatusFixed,
1070 : : {
1071 : : {errs1[0]->layerId(), errs1[0]->featureId(), QgsGeometryCheck::ChangePart, QgsGeometryCheck::ChangeRemoved, QgsVertexId( 0 )},
1072 : : {errs1[0]->layerId(), errs1[0]->featureId(), QgsGeometryCheck::ChangePart, QgsGeometryCheck::ChangeAdded, QgsVertexId( 0 )},
1073 : : {errs1[0]->layerId(), nextId, QgsGeometryCheck::ChangeFeature, QgsGeometryCheck::ChangeAdded, QgsVertexId()}
1074 : : } ) );
1075 : 1 : testContext.second[errs1[0]->layerId()]->getFeature( errs1[0]->featureId(), f );
1076 : 1 : QCOMPARE( f.geometry().constGet()->partCount(), 1 );
1077 : 1 : QCOMPARE( f.geometry().constGet()->vertexCount(), 4 );
1078 : 1 : testContext.second[errs1[0]->layerId()]->getFeature( nextId, f );
1079 : 1 : QCOMPARE( f.geometry().constGet()->partCount(), 1 );
1080 : 1 : QCOMPARE( f.geometry().constGet()->vertexCount(), 6 );
1081 : :
1082 : 3 : QVERIFY( fixCheckError( testContext.second, errs2[0],
1083 : : QgsGeometrySelfIntersectionCheck::ToMultiObject, QgsGeometryCheckError::StatusFixed,
1084 : : {
1085 : : {errs2[0]->layerId(), errs2[0]->featureId(), QgsGeometryCheck::ChangePart, QgsGeometryCheck::ChangeRemoved, QgsVertexId( 0 )},
1086 : : {errs2[0]->layerId(), errs2[0]->featureId(), QgsGeometryCheck::ChangePart, QgsGeometryCheck::ChangeAdded, QgsVertexId( 0 )},
1087 : : {errs2[0]->layerId(), errs2[0]->featureId(), QgsGeometryCheck::ChangePart, QgsGeometryCheck::ChangeAdded, QgsVertexId( 1 )}
1088 : : } ) );
1089 : 1 : testContext.second[errs2[0]->layerId()]->getFeature( errs2[0]->featureId(), f );
1090 : 1 : QCOMPARE( f.geometry().constGet()->partCount(), 2 );
1091 : 1 : QCOMPARE( f.geometry().constGet()->vertexCount( 0 ), 4 );
1092 : 1 : QCOMPARE( f.geometry().constGet()->vertexCount( 1 ), 5 );
1093 : :
1094 : 1 : nextId = testContext.second[errs3[0]->layerId()]->layer()->featureCount();
1095 : 2 : QVERIFY( fixCheckError( testContext.second, errs3[0],
1096 : : QgsGeometrySelfIntersectionCheck::ToSingleObjects, QgsGeometryCheckError::StatusFixed,
1097 : : {
1098 : : {errs3[0]->layerId(), errs3[0]->featureId(), QgsGeometryCheck::ChangeRing, QgsGeometryCheck::ChangeChanged, QgsVertexId( 0, 0 )},
1099 : : {errs3[0]->layerId(), nextId, QgsGeometryCheck::ChangeFeature, QgsGeometryCheck::ChangeAdded, QgsVertexId()}
1100 : : } ) );
1101 : 1 : testContext.second[errs3[0]->layerId()]->getFeature( errs3[0]->featureId(), f );
1102 : 1 : QCOMPARE( f.geometry().constGet()->partCount(), 1 );
1103 : 1 : QCOMPARE( f.geometry().constGet()->vertexCount(), 6 );
1104 : 1 : testContext.second[errs3[0]->layerId()]->getFeature( nextId, f );
1105 : 1 : QCOMPARE( f.geometry().constGet()->partCount(), 1 );
1106 : 1 : QCOMPARE( f.geometry().constGet()->vertexCount(), 4 );
1107 : :
1108 : 3 : QVERIFY( fixCheckError( testContext.second, errs4[0],
1109 : : QgsGeometrySelfIntersectionCheck::ToMultiObject, QgsGeometryCheckError::StatusFixed,
1110 : : {
1111 : : {errs4[0]->layerId(), errs4[0]->featureId(), QgsGeometryCheck::ChangeRing, QgsGeometryCheck::ChangeChanged, QgsVertexId( 0, 0 )},
1112 : : {errs4[0]->layerId(), errs4[0]->featureId(), QgsGeometryCheck::ChangeRing, QgsGeometryCheck::ChangeRemoved, QgsVertexId( 0, 1 )},
1113 : : {errs4[0]->layerId(), errs4[0]->featureId(), QgsGeometryCheck::ChangePart, QgsGeometryCheck::ChangeAdded, QgsVertexId( 1 )}
1114 : : } ) );
1115 : 1 : testContext.second[errs4[0]->layerId()]->getFeature( errs4[0]->featureId(), f );
1116 : 1 : QCOMPARE( f.geometry().constGet()->partCount(), 2 );
1117 : 1 : QCOMPARE( f.geometry().constGet()->ringCount( 0 ), 1 );
1118 : 1 : QCOMPARE( f.geometry().constGet()->vertexCount( 0, 0 ), 5 );
1119 : 1 : QCOMPARE( f.geometry().constGet()->ringCount( 1 ), 2 );
1120 : 1 : QCOMPARE( f.geometry().constGet()->vertexCount( 1, 0 ), 5 );
1121 : 1 : QCOMPARE( f.geometry().constGet()->vertexCount( 1, 1 ), 5 );
1122 : :
1123 : : // Test change tracking
1124 : 1 : QgsGeometryCheckErrorSingle *err = static_cast<QgsGeometryCheckErrorSingle *>( errs4[0] );
1125 : 1 : QgsGeometryUtils::SelfIntersection oldInter = static_cast<QgsGeometrySelfIntersectionCheckError *>( err->singleError() )->intersection();
1126 : 1 : QVERIFY( err->handleChanges( change2changes( {err->layerId(), err->featureId(), QgsGeometryCheck::ChangeNode, QgsGeometryCheck::ChangeRemoved, QgsVertexId( err->vidx().part, errs4[0]->vidx().ring, 0 )} ) ) );
1127 : 1 : QgsGeometryUtils::SelfIntersection newInter = static_cast<QgsGeometrySelfIntersectionCheckError *>( err->singleError() )->intersection();
1128 : 1 : QVERIFY( oldInter.segment1 == newInter.segment1 + 1 );
1129 : 1 : QVERIFY( oldInter.segment2 == newInter.segment2 + 1 );
1130 : 1 : QVERIFY( err->handleChanges( change2changes( {err->layerId(), errs4[0]->featureId(), QgsGeometryCheck::ChangeNode, QgsGeometryCheck::ChangeAdded, QgsVertexId( err->vidx().part, errs4[0]->vidx().ring, 0 )} ) ) );
1131 : 1 : newInter = static_cast<QgsGeometrySelfIntersectionCheckError *>( err->singleError() )->intersection();
1132 : 1 : QVERIFY( oldInter.segment1 == newInter.segment1 );
1133 : 1 : QVERIFY( oldInter.segment2 == newInter.segment2 );
1134 : :
1135 : 1 : cleanupTestContext( testContext );
1136 : 1 : }
1137 : :
1138 : 1 : void TestQgsGeometryChecks::testSliverPolygonCheck()
1139 : : {
1140 : 1 : QTemporaryDir dir;
1141 : 1 : QMap<QString, QString> layers;
1142 : 1 : layers.insert( "point_layer.shp", "" );
1143 : 1 : layers.insert( "line_layer.shp", "" );
1144 : 1 : layers.insert( "polygon_layer.shp", "" );
1145 : 1 : auto testContext = createTestContext( dir, layers );
1146 : :
1147 : : // Test detection
1148 : 1 : QList<QgsGeometryCheckError *> checkErrors;
1149 : 1 : QStringList messages;
1150 : :
1151 : 1 : QVariantMap configuration;
1152 : 1 : configuration.insert( "threshold", 20 );
1153 : 1 : configuration.insert( "maxArea", 0.04 );
1154 : :
1155 : 1 : QgsFeedback feedback;
1156 : 1 : QgsGeometrySliverPolygonCheck( testContext.first, configuration ).collectErrors( testContext.second, checkErrors, messages, &feedback );
1157 : 1 : listErrors( checkErrors, messages );
1158 : :
1159 : 1 : QCOMPARE( checkErrors.size(), 2 );
1160 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["point_layer.shp"] ).isEmpty() );
1161 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"] ).isEmpty() );
1162 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["polygon_layer.shp"], 6, QgsPointXY(), QgsVertexId( 0 ) ).size() == 1 );
1163 : 1 : QVERIFY( searchCheckErrors( checkErrors, layers["polygon_layer.shp"], 11, QgsPointXY(), QgsVertexId( 0 ) ).size() == 1 );
1164 : :
1165 : : // The fix methods are exactely the same as in QgsGeometryAreaCheck, no point repeating...
1166 : :
1167 : 1 : cleanupTestContext( testContext );
1168 : 1 : }
1169 : :
1170 : 1 : void TestQgsGeometryChecks::testGapCheckPointInPoly()
1171 : : {
1172 : : // The case where the gap was containing a point that was lying inside (or on the edge)
1173 : : // of a neighbouring polygon used to fail, as we were using that neighbour's points to
1174 : : // snap to (since it was also at distance 0). This was leading to flaky wrong results.
1175 : :
1176 : 1 : QTemporaryDir dir;
1177 : 1 : QMap<QString, QString> layers;
1178 : 1 : layers.insert( "gap_layer_point_in_poly.shp", "" );
1179 : 1 : auto testContext = createTestContext( dir, layers );
1180 : :
1181 : : // Test detection
1182 : 1 : QList<QgsGeometryCheckError *> checkErrors;
1183 : 1 : QStringList messages;
1184 : :
1185 : 1 : QVariantMap configuration;
1186 : :
1187 : 1 : QgsProject::instance()->setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 2056 ) );
1188 : :
1189 : 1 : QgsGeometryGapCheck check( testContext.first, configuration );
1190 : 1 : QgsFeedback feedback;
1191 : 1 : check.collectErrors( testContext.second, checkErrors, messages, &feedback );
1192 : 1 : listErrors( checkErrors, messages );
1193 : :
1194 : 1 : QCOMPARE( checkErrors.size(), 1 );
1195 : :
1196 : 1 : QgsGeometryCheckError *error = checkErrors.first();
1197 : 1 : QCOMPARE( error->contextBoundingBox().snappedToGrid( 100.0 ), QgsRectangle( 2.5372e+06, 1.1522e+06, 2.5375e+06, 1.1524e+06 ) );
1198 : 1 : QCOMPARE( error->affectedAreaBBox().snappedToGrid( 100.0 ), QgsRectangle( 2.5373e+06, 1.1523e+06, 2.5375e+06, 1.1523e+06 ) );
1199 : :
1200 : : // Test fixes
1201 : 1 : QgsFeature f;
1202 : 1 : testContext.second[layers["gap_layer_point_in_poly.shp"]]->getFeature( 1, f );
1203 : 1 : double areaOld = f.geometry().area();
1204 : 1 : QCOMPARE( areaOld, 19913.135772452362 );
1205 : :
1206 : 1 : QgsGeometryCheck::Changes changes;
1207 : 1 : QMap<QString, int> mergeAttrs;
1208 : 1 : error->check()->fixError( testContext.second, error, QgsGeometryGapCheck::MergeLongestEdge, mergeAttrs, changes );
1209 : :
1210 : : // Ensure it worked
1211 : 1 : QCOMPARE( error->status(), QgsGeometryCheckError::StatusFixed );
1212 : :
1213 : : // Ensure it worked on geom
1214 : 1 : testContext.second[layers["gap_layer_point_in_poly.shp"]]->getFeature( 1, f );
1215 : 1 : QVERIFY( f.geometry().area() > areaOld );
1216 : :
1217 : 1 : cleanupTestContext( testContext );
1218 : 1 : }
1219 : :
1220 : 1 : void TestQgsGeometryChecks::testOverlapCheckToleranceBug()
1221 : : {
1222 : : // The overlap (intersection) was computed with a different tolerance when collecting errors
1223 : : // than when fixing them, leading to failures to fix the issue esp. with big coordinates.
1224 : : //
1225 : : // Also, it used to offset unaffected points (far from the actual overlap) on the affected
1226 : : // feature, leading to both unwanted shifts and remaining slivers.
1227 : :
1228 : 1 : QTemporaryDir dir;
1229 : 1 : QMap<QString, QString> layers;
1230 : 1 : layers.insert( "overlap_layer_tolerance_bug.shp", "" );
1231 : 1 : auto testContext = createTestContext( dir, layers );
1232 : :
1233 : : // Test detection
1234 : 1 : QList<QgsGeometryCheckError *> checkErrors;
1235 : 1 : QStringList messages;
1236 : :
1237 : 1 : QVariantMap configuration;
1238 : 1 : configuration.insert( "gapThreshold", 1000.0 );
1239 : :
1240 : 1 : QgsProject::instance()->setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 2056 ) );
1241 : :
1242 : 1 : QgsGeometryOverlapCheck check( testContext.first, configuration );
1243 : 1 : QgsFeedback feedback;
1244 : 1 : check.collectErrors( testContext.second, checkErrors, messages, &feedback );
1245 : 1 : listErrors( checkErrors, messages );
1246 : :
1247 : 1 : QCOMPARE( checkErrors.size(), 1 );
1248 : :
1249 : 1 : QgsGeometryCheckError *error = checkErrors.first();
1250 : :
1251 : : // Test fixes
1252 : 1 : QgsFeature f;
1253 : 1 : testContext.second[layers["overlap_layer_tolerance_bug.shp"]]->getFeature( 0, f );
1254 : 1 : double areaOld = f.geometry().area();
1255 : 1 : QgsPoint pointOld_1 = f.geometry().vertexAt( 1 );
1256 : 1 : QgsPoint pointOld_2 = f.geometry().vertexAt( 2 );
1257 : :
1258 : : // Just making sure we've got the right feature/point
1259 : 1 : QCOMPARE( areaOld, 10442.710061549426 );
1260 : 1 : QGSCOMPARENEARPOINT( pointOld_1, QgsPoint( 2537221.53079314017668366, 1152360.02460834058001637 ), 0.00001 );
1261 : 1 : QGSCOMPARENEARPOINT( pointOld_2, QgsPoint( 2537366.84566075634211302, 1152360.28978145681321621 ), 0.00001 );
1262 : :
1263 : 1 : QgsGeometryCheck::Changes changes;
1264 : 1 : QMap<QString, int> mergeAttrs;
1265 : 1 : error->check()->fixError( testContext.second, error, QgsGeometryOverlapCheck::Subtract, mergeAttrs, changes );
1266 : :
1267 : : // Ensure it worked
1268 : 1 : QCOMPARE( error->status(), QgsGeometryCheckError::StatusFixed );
1269 : :
1270 : : // Ensure it actually worked
1271 : 1 : testContext.second[layers["overlap_layer_tolerance_bug.shp"]]->getFeature( 0, f );
1272 : 1 : QVERIFY( f.geometry().area() < areaOld );
1273 : : if ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR < 9 )
1274 : : {
1275 : : // And that we don't have unexpected changes on unaffected points
1276 : : QCOMPARE( f.geometry().vertexAt( 1 ), pointOld_1 );
1277 : : QCOMPARE( f.geometry().vertexAt( 2 ), pointOld_2 );
1278 : : }
1279 : : else
1280 : : {
1281 : : /* For reference
1282 : : qDebug() << f.geometry().vertexAt( 1 ).asWkt() << "\n"; // "Point (2537366.84566075634211302 1152360.28978145681321621)"
1283 : : qDebug() << pointOld_1.asWkt() << "\n"; // "Point (2537221.53079314017668366 1152360.02460834058001637)"
1284 : : qDebug() << f.geometry().vertexAt( 2 ).asWkt() << "\n"; // "Point (2537297.08237999258562922 1152290.78251273254863918)"
1285 : : qDebug() << pointOld_2.asWkt() << "\n"; // "Point (2537366.84566075634211302 1152360.28978145681321621)"
1286 : : */
1287 : 2 : QCOMPARE( f.geometry().vertexAt( 1 ).asWkt( 4 ), QStringLiteral( "Point (2537366.8457 1152360.2898)" ) );
1288 : 2 : QCOMPARE( f.geometry().vertexAt( 2 ).asWkt( 4 ), QStringLiteral( "Point (2537297.0824 1152290.7825)" ) );
1289 : : }
1290 : :
1291 : 1 : cleanupTestContext( testContext );
1292 : 1 : }
1293 : :
1294 : : ///////////////////////////////////////////////////////////////////////////////
1295 : :
1296 : 0 : double TestQgsGeometryChecks::layerToMapUnits( const QgsMapLayer *layer, const QgsCoordinateReferenceSystem &mapCrs ) const
1297 : : {
1298 : 0 : QgsCoordinateTransform crst = QgsCoordinateTransform( layer->crs(), mapCrs, QgsProject::instance() );
1299 : 0 : QgsRectangle extent = layer->extent();
1300 : 0 : QgsPointXY l1( extent.xMinimum(), extent.yMinimum() );
1301 : 0 : QgsPointXY l2( extent.xMaximum(), extent.yMaximum() );
1302 : 0 : double distLayerUnits = std::sqrt( l1.sqrDist( l2 ) );
1303 : 0 : QgsPointXY m1 = crst.transform( l1 );
1304 : 0 : QgsPointXY m2 = crst.transform( l2 );
1305 : 0 : double distMapUnits = std::sqrt( m1.sqrDist( m2 ) );
1306 : 0 : return distMapUnits / distLayerUnits;
1307 : 0 : }
1308 : :
1309 : 64 : QgsFeaturePool *TestQgsGeometryChecks::createFeaturePool( QgsVectorLayer *layer, bool selectedOnly ) const
1310 : : {
1311 : 64 : return new QgsVectorDataProviderFeaturePool( layer, selectedOnly );
1312 : 0 : }
1313 : :
1314 : 25 : QPair<QgsGeometryCheckContext *, QMap<QString, QgsFeaturePool *> > TestQgsGeometryChecks::createTestContext( QTemporaryDir &tempDir, QMap<QString, QString> &layers, const QgsCoordinateReferenceSystem &mapCrs, double prec ) const
1315 : : {
1316 : 25 : QDir testDataDir( QDir( TEST_DATA_DIR ).absoluteFilePath( "geometry_checker" ) );
1317 : 25 : QDir tmpDir( tempDir.path() );
1318 : :
1319 : 25 : QMap<QString, QgsFeaturePool *> featurePools;
1320 : 89 : for ( const QString &layerFile : layers.keys() )
1321 : : {
1322 : 64 : QFile( testDataDir.absoluteFilePath( layerFile ) ).copy( tmpDir.absoluteFilePath( layerFile ) );
1323 : 64 : if ( layerFile.endsWith( ".shp", Qt::CaseInsensitive ) )
1324 : : {
1325 : 63 : QString baseName = QFileInfo( layerFile ).baseName();
1326 : 63 : QFile( testDataDir.absoluteFilePath( baseName + ".dbf" ) ).copy( tmpDir.absoluteFilePath( baseName + ".dbf" ) );
1327 : 63 : QFile( testDataDir.absoluteFilePath( baseName + ".pri" ) ).copy( tmpDir.absoluteFilePath( baseName + ".pri" ) );
1328 : 63 : QFile( testDataDir.absoluteFilePath( baseName + ".qpj" ) ).copy( tmpDir.absoluteFilePath( baseName + ".qpj" ) );
1329 : 63 : QFile( testDataDir.absoluteFilePath( baseName + ".shx" ) ).copy( tmpDir.absoluteFilePath( baseName + ".shx" ) );
1330 : 63 : }
1331 : 64 : QgsVectorLayer *layer = new QgsVectorLayer( tmpDir.absoluteFilePath( layerFile ), layerFile );
1332 : : Q_ASSERT( layer && layer->isValid() );
1333 : 64 : layers[layerFile] = layer->id();
1334 : 64 : layer->dataProvider()->enterUpdateMode();
1335 : 64 : featurePools.insert( layer->id(), createFeaturePool( layer ) );
1336 : : }
1337 : 25 : return qMakePair( new QgsGeometryCheckContext( prec, mapCrs, QgsProject::instance()->transformContext(), QgsProject::instance() ), featurePools );
1338 : 25 : }
1339 : :
1340 : 24 : void TestQgsGeometryChecks::cleanupTestContext( QPair<QgsGeometryCheckContext *, QMap<QString, QgsFeaturePool *> > ctx ) const
1341 : : {
1342 : 85 : for ( const QgsFeaturePool *pool : ctx.second )
1343 : : {
1344 : 61 : pool->layer()->dataProvider()->leaveUpdateMode();
1345 : 61 : delete pool->layer();
1346 : : }
1347 : 24 : qDeleteAll( ctx.second );
1348 : 24 : delete ctx.first;
1349 : 24 : }
1350 : :
1351 : 27 : void TestQgsGeometryChecks::listErrors( const QList<QgsGeometryCheckError *> &checkErrors, const QStringList &messages ) const
1352 : : {
1353 : 27 : QTextStream( stdout ) << " - Check result:" << endl;
1354 : 158 : for ( const QgsGeometryCheckError *error : checkErrors )
1355 : : {
1356 : 131 : QTextStream( stdout ) << " * " << error->layerId() << ":" << error->featureId() << " @[" << error->vidx().part << ", " << error->vidx().ring << ", " << error->vidx().vertex << "](" << error->location().x() << ", " << error->location().y() << ") = " << error->value().toString() << endl;
1357 : : }
1358 : 27 : if ( !messages.isEmpty() )
1359 : : {
1360 : 5 : QTextStream( stdout ) << " - Check messages:" << endl << " * " << messages.join( "\n * " ) << endl;
1361 : 5 : }
1362 : 27 : }
1363 : :
1364 : 110 : QList<QgsGeometryCheckError *> TestQgsGeometryChecks::searchCheckErrors( const QList<QgsGeometryCheckError *> &checkErrors, const QString &layerId, const QgsFeatureId &featureId, const QgsPointXY &pos, const QgsVertexId &vid, const QVariant &value, double tol ) const
1365 : : {
1366 : 110 : QList<QgsGeometryCheckError *> matching;
1367 : 820 : for ( QgsGeometryCheckError *error : checkErrors )
1368 : : {
1369 : 710 : if ( error->layerId() != layerId )
1370 : : {
1371 : 312 : continue;
1372 : : }
1373 : 398 : if ( featureId != -1 && error->featureId() != featureId )
1374 : : {
1375 : 239 : continue;
1376 : : }
1377 : 159 : if ( !pos.isEmpty() && ( !qgsDoubleNear( error->location().x(), pos.x(), tol ) || !qgsDoubleNear( error->location().y(), pos.y(), tol ) ) )
1378 : : {
1379 : 50 : continue;
1380 : : }
1381 : 109 : if ( vid.isValid() && vid != error->vidx() )
1382 : : {
1383 : 0 : continue;
1384 : : }
1385 : 109 : if ( !value.isNull() )
1386 : : {
1387 : 40 : if ( value.type() == QVariant::Double )
1388 : : {
1389 : 26 : if ( !qgsDoubleNear( value.toDouble(), error->value().toDouble(), tol ) )
1390 : : {
1391 : 0 : continue;
1392 : : }
1393 : 26 : }
1394 : 14 : else if ( value != error->value() )
1395 : : {
1396 : 0 : continue;
1397 : : }
1398 : 40 : }
1399 : 109 : matching.append( error );
1400 : : }
1401 : 110 : return matching;
1402 : 110 : }
1403 : :
1404 : 18 : bool TestQgsGeometryChecks::fixCheckError( QMap<QString, QgsFeaturePool *> featurePools, QgsGeometryCheckError *error, int method, const QgsGeometryCheckError::Status &expectedStatus, const QVector<Change> &expectedChanges, const QMap<QString, int> &mergeAttrs )
1405 : : {
1406 : 18 : QTextStream( stdout ) << " - Fixing " << error->layerId() << ":" << error->featureId() << " @[" << error->vidx().part << ", " << error->vidx().ring << ", " << error->vidx().vertex << "](" << error->location().x() << ", " << error->location().y() << ") = " << error->value().toString() << endl;
1407 : 18 : QgsGeometryCheck::Changes changes;
1408 : 18 : error->check()->fixError( featurePools, error, method, mergeAttrs, changes );
1409 : 18 : QTextStream( stdout ) << " * Fix status: " << error->status() << endl;
1410 : 18 : if ( error->status() != expectedStatus )
1411 : : {
1412 : 0 : return false;
1413 : : }
1414 : 18 : QString strChangeWhat[] = { "ChangeFeature", "ChangePart", "ChangeRing", "ChangeNode" };
1415 : 18 : QString strChangeType[] = { "ChangeAdded", "ChangeRemoved", "ChangeChanged" };
1416 : 18 : int totChanges = 0;
1417 : 36 : for ( const QString &layerId : changes.keys() )
1418 : : {
1419 : 41 : for ( const QgsFeatureId &fid : changes[layerId].keys() )
1420 : : {
1421 : 55 : for ( const QgsGeometryCheck::Change &change : changes[layerId][fid] )
1422 : : {
1423 : 32 : QTextStream( stdout ) << " * Change: " << layerId << ":" << fid << " :: " << strChangeWhat[change.what] << ", " << strChangeType[change.type] << ", " << change.vidx.part << ":" << change.vidx.ring << ":" << change.vidx.vertex << endl;
1424 : : }
1425 : 23 : totChanges += changes[layerId][fid].size();
1426 : : }
1427 : : }
1428 : 18 : QTextStream( stdout ) << " * Num changes: " << totChanges << ", expected num changes: " << expectedChanges.size() << endl;
1429 : 18 : if ( expectedChanges.size() != totChanges )
1430 : : {
1431 : 0 : return false;
1432 : : }
1433 : 50 : for ( const Change &expectedChange : expectedChanges )
1434 : : {
1435 : 32 : if ( !changes.contains( expectedChange.layerId ) )
1436 : : {
1437 : 0 : return false;
1438 : : }
1439 : 32 : if ( !changes[expectedChange.layerId].contains( expectedChange.fid ) )
1440 : : {
1441 : 0 : return false;
1442 : : }
1443 : 32 : QgsGeometryCheck::Change change( expectedChange.what, expectedChange.type, expectedChange.vidx );
1444 : 32 : if ( !changes[expectedChange.layerId][expectedChange.fid].contains( change ) )
1445 : : {
1446 : 0 : return false;
1447 : : }
1448 : : }
1449 : 18 : return true;
1450 : 126 : }
1451 : :
1452 : 18 : QgsGeometryCheck::Changes TestQgsGeometryChecks::change2changes( const Change &change ) const
1453 : : {
1454 : 18 : QgsGeometryCheck::Changes changes;
1455 : 18 : QMap<QgsFeatureId, QList<QgsGeometryCheck::Change>> featureChanges;
1456 : 18 : featureChanges.insert( change.fid, {QgsGeometryCheck::Change( change.what, change.type, change.vidx )} );
1457 : 18 : changes.insert( change.layerId, featureChanges );
1458 : 18 : return changes;
1459 : 18 : }
1460 : :
1461 : 1 : QGSTEST_MAIN( TestQgsGeometryChecks )
1462 : : #include "testqgsgeometrychecks.moc"
|