Skip to content

Commit 2275853

Browse files
authored
Merge pull request #8008 from m-kuhn/missingVertexCheck
Add QgsGeometryMissingVertexCheck
2 parents b85db09 + 3c379d3 commit 2275853

File tree

5 files changed

+216
-0
lines changed

5 files changed

+216
-0
lines changed

src/analysis/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ SET(QGIS_ANALYSIS_SRCS
149149
vector/geometry_checker/qgsgeometryduplicatenodescheck.cpp
150150
vector/geometry_checker/qgsgeometrygapcheck.cpp
151151
vector/geometry_checker/qgsgeometryfollowboundariescheck.cpp
152+
vector/geometry_checker/qgsgeometrymissingvertexcheck.cpp
152153
vector/geometry_checker/qgsgeometryholecheck.cpp
153154
vector/geometry_checker/qgsgeometrylineintersectioncheck.cpp
154155
vector/geometry_checker/qgsgeometrylinelayerintersectioncheck.cpp
@@ -277,6 +278,7 @@ SET(QGIS_ANALYSIS_HDRS
277278
vector/geometry_checker/qgsgeometryduplicatenodescheck.h
278279
vector/geometry_checker/qgsgeometryfollowboundariescheck.h
279280
vector/geometry_checker/qgsgeometrygapcheck.h
281+
vector/geometry_checker/qgsgeometrymissingvertexcheck.h
280282
vector/geometry_checker/qgsgeometryholecheck.h
281283
vector/geometry_checker/qgsgeometrylineintersectioncheck.h
282284
vector/geometry_checker/qgsgeometrylinelayerintersectioncheck.h
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/***************************************************************************
2+
qgsgeometrymissingvertexcheck.cpp
3+
---------------------
4+
begin : September 2018
5+
copyright : (C) 2018 Matthias Kuhn
6+
email : matthias@opengis.ch
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#include "qgsgeometrymissingvertexcheck.h"
17+
18+
#include "qgsfeaturepool.h"
19+
#include "qgsgeometrycollection.h"
20+
#include "qgsmultipolygon.h"
21+
#include "qgscurvepolygon.h"
22+
#include "qgscurve.h"
23+
#include "qgslinestring.h"
24+
#include "qgsgeometryengine.h"
25+
#include "qgsgeometryutils.h"
26+
27+
void QgsGeometryMissingVertexCheck::collectErrors( QList<QgsGeometryCheckError *> &errors, QStringList &messages, QAtomicInt *progressCounter, const QMap<QString, QgsFeatureIds> &ids ) const
28+
{
29+
Q_UNUSED( messages );
30+
if ( progressCounter )
31+
progressCounter->fetchAndAddRelaxed( 1 );
32+
33+
QMap<QString, QgsFeatureIds> featureIds = ids.isEmpty() ? allLayerFeatureIds() : ids;
34+
35+
QgsFeaturePool *featurePool = mContext->featurePools.value( featureIds.firstKey() );
36+
37+
const QgsGeometryCheckerUtils::LayerFeatures layerFeatures( mContext->featurePools, featureIds, mCompatibleGeometryTypes, nullptr, mContext, true );
38+
39+
for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeature : layerFeatures )
40+
{
41+
const QgsAbstractGeometry *geom = layerFeature.geometry().constGet();
42+
43+
if ( QgsCurvePolygon *polygon = qgsgeometry_cast<QgsCurvePolygon *>( geom ) )
44+
{
45+
processPolygon( polygon, featurePool, errors, layerFeature );
46+
}
47+
else if ( QgsGeometryCollection *collection = qgsgeometry_cast<QgsGeometryCollection *>( geom ) )
48+
{
49+
const int numGeometries = collection->numGeometries();
50+
for ( int i = 0; i < numGeometries; ++i )
51+
{
52+
if ( QgsCurvePolygon *polygon = qgsgeometry_cast<QgsCurvePolygon *>( collection->geometryN( i ) ) )
53+
{
54+
processPolygon( polygon, featurePool, errors, layerFeature );
55+
}
56+
}
57+
}
58+
}
59+
}
60+
61+
void QgsGeometryMissingVertexCheck::fixError( QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const
62+
{
63+
Q_UNUSED( mergeAttributeIndices );
64+
Q_UNUSED( changes );
65+
if ( method == NoChange )
66+
{
67+
error->setFixed( method );
68+
}
69+
}
70+
71+
QStringList QgsGeometryMissingVertexCheck::resolutionMethods() const
72+
{
73+
static QStringList methods = QStringList() << tr( "No action" );
74+
return methods;
75+
}
76+
77+
void QgsGeometryMissingVertexCheck::processPolygon( const QgsCurvePolygon *polygon, QgsFeaturePool *featurePool, QList<QgsGeometryCheckError *> &errors, const QgsGeometryCheckerUtils::LayerFeature &layerFeature ) const
78+
{
79+
const QgsFeature &currentFeature = layerFeature.feature();
80+
std::unique_ptr<QgsMultiPolygon> boundaries = qgis::make_unique<QgsMultiPolygon>();
81+
82+
std::unique_ptr< QgsGeometryEngine > geomEngine = QgsGeometryCheckerUtils::createGeomEngine( polygon->exteriorRing(), mContext->tolerance );
83+
boundaries->addGeometry( geomEngine->buffer( mContext->tolerance, 5 ) );
84+
85+
const int numRings = polygon->numInteriorRings();
86+
for ( int i = 0; i < numRings; ++i )
87+
{
88+
geomEngine = QgsGeometryCheckerUtils::createGeomEngine( polygon->exteriorRing(), mContext->tolerance );
89+
boundaries->addGeometry( geomEngine->buffer( mContext->tolerance, 5 ) );
90+
}
91+
92+
geomEngine = QgsGeometryCheckerUtils::createGeomEngine( boundaries.get(), mContext->tolerance );
93+
geomEngine->prepareGeometry();
94+
95+
const QgsFeatureIds fids = featurePool->getIntersects( boundaries->boundingBox() );
96+
97+
QgsFeature compareFeature;
98+
for ( QgsFeatureId fid : fids )
99+
{
100+
if ( fid == currentFeature.id() )
101+
continue;
102+
103+
if ( featurePool->getFeature( fid, compareFeature ) )
104+
{
105+
QgsVertexIterator vertexIterator = compareFeature.geometry().vertices();
106+
while ( vertexIterator.hasNext() )
107+
{
108+
const QgsPoint &pt = vertexIterator.next();
109+
if ( geomEngine->intersects( &pt ) )
110+
{
111+
QgsVertexId vertexId;
112+
QgsPoint closestVertex = QgsGeometryUtils::closestVertex( *polygon, pt, vertexId );
113+
114+
if ( closestVertex.distance( pt ) > mContext->tolerance )
115+
{
116+
bool alreadyReported = false;
117+
for ( QgsGeometryCheckError *error : qgis::as_const( errors ) )
118+
{
119+
// Only list missing vertices once
120+
if ( error->featureId() == currentFeature.id() && error->location() == QgsPointXY( pt ) )
121+
{
122+
alreadyReported = true;
123+
break;
124+
}
125+
}
126+
if ( !alreadyReported )
127+
errors.append( new QgsGeometryCheckError( this, layerFeature, QgsPointXY( pt ) ) );
128+
}
129+
}
130+
}
131+
}
132+
}
133+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/***************************************************************************
2+
qgsgeometrymissingvertexcheck.h
3+
---------------------
4+
begin : September 2018
5+
copyright : (C) 2018 Matthias Kuhn
6+
email : matthias@opengis.ch
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#define SIP_NO_FILE
17+
18+
#ifndef QGSGEOMETRYMISSINGVERTEXCHECK_H
19+
#define QGSGEOMETRYMISSINGVERTEXCHECK_H
20+
21+
#include "qgsgeometrycheck.h"
22+
23+
class QgsCurvePolygon;
24+
25+
/**
26+
* \ingroup analysis
27+
*
28+
* A topology check for missing vertices.
29+
* Any vertex which is on the border of another polygon but no corresponding vertex
30+
* can be found on the other polygon will be reported as an error.
31+
*
32+
* \since QGIS 3.4
33+
*/
34+
class ANALYSIS_EXPORT QgsGeometryMissingVertexCheck : public QgsGeometryCheck
35+
{
36+
public:
37+
explicit QgsGeometryMissingVertexCheck( QgsGeometryCheckerContext *context )
38+
: QgsGeometryCheck( LayerCheck, {QgsWkbTypes::PolygonGeometry}, context )
39+
{}
40+
void collectErrors( QList<QgsGeometryCheckError *> &errors, QStringList &messages, QAtomicInt *progressCounter = nullptr, const QMap<QString, QgsFeatureIds> &ids = QMap<QString, QgsFeatureIds>() ) const override;
41+
void fixError( QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override;
42+
QStringList resolutionMethods() const override;
43+
QString errorDescription() const override { return tr( "Missing Vertex" ); }
44+
QString errorName() const override { return QStringLiteral( "QgsGeometryMissingVertexCheck" ); }
45+
46+
enum ResolutionMethod { NoChange };
47+
48+
private:
49+
void processPolygon( const QgsCurvePolygon *polygon, QgsFeaturePool *featurePool, QList<QgsGeometryCheckError *> &errors, const QgsGeometryCheckerUtils::LayerFeature &layerFeature ) const;
50+
};
51+
52+
#endif // QGSGEOMETRYMISSINGVERTEXCHECK_

tests/src/geometry_checker/testqgsgeometrychecks.cpp

+29
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "qgsgeometryfollowboundariescheck.h"
2929
#include "qgsgeometrygapcheck.h"
3030
#include "qgsgeometryholecheck.h"
31+
#include "qgsgeometrymissingvertexcheck.h"
3132
#include "qgsgeometrylineintersectioncheck.h"
3233
#include "qgsgeometrylinelayerintersectioncheck.h"
3334
#include "qgsgeometrymultipartcheck.h"
@@ -81,6 +82,7 @@ class TestQgsGeometryChecks: public QObject
8182
void testDuplicateNodesCheck();
8283
void testFollowBoundariesCheck();
8384
void testGapCheck();
85+
void testMissingVertexCheck();
8486
void testHoleCheck();
8587
void testLineIntersectionCheck();
8688
void testLineLayerIntersectionCheck();
@@ -530,6 +532,33 @@ void TestQgsGeometryChecks::testGapCheck()
530532
cleanupTestContext( context );
531533
}
532534

535+
void TestQgsGeometryChecks::testMissingVertexCheck()
536+
{
537+
QTemporaryDir dir;
538+
QMap<QString, QString> layers;
539+
layers.insert( QStringLiteral( "missing_vertex.gpkg" ), QString() );
540+
QgsGeometryCheckerContext *context = createTestContext( dir, layers );
541+
542+
// Test detection
543+
QList<QgsGeometryCheckError *> checkErrors;
544+
QStringList messages;
545+
546+
QgsGeometryMissingVertexCheck check( context );
547+
check.collectErrors( checkErrors, messages );
548+
listErrors( checkErrors, messages );
549+
550+
const QString layerId = layers.values().first();
551+
QVERIFY( searchCheckErrors( checkErrors, layerId, 0, QgsPointXY( 0.251153, -0.460895 ), QgsVertexId() ).size() == 1 );
552+
QVERIFY( searchCheckErrors( checkErrors, layerId, 3, QgsPointXY( 0.257985, -0.932886 ), QgsVertexId() ).size() == 1 );
553+
QVERIFY( searchCheckErrors( checkErrors, layerId, 5, QgsPointXY( 0.59781, -0.480033 ), QgsVertexId() ).size() == 1 );
554+
QVERIFY( searchCheckErrors( checkErrors, layerId, 5, QgsPointXY( 0.605252, -0.664875 ), QgsVertexId() ).size() == 1 );
555+
QVERIFY( searchCheckErrors( checkErrors, layerId, 4, QgsPointXY( 0.259197, -0.478311 ), QgsVertexId() ).size() == 1 );
556+
557+
QCOMPARE( checkErrors.size(), 5 );
558+
559+
cleanupTestContext( context );
560+
}
561+
533562
void TestQgsGeometryChecks::testHoleCheck()
534563
{
535564
QTemporaryDir dir;
Binary file not shown.

0 commit comments

Comments
 (0)