Skip to content

Commit

Permalink
Merge pull request #8008 from m-kuhn/missingVertexCheck
Browse files Browse the repository at this point in the history
Add QgsGeometryMissingVertexCheck
  • Loading branch information
m-kuhn committed Sep 27, 2018
2 parents b85db09 + 3c379d3 commit 2275853
Show file tree
Hide file tree
Showing 5 changed files with 216 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/analysis/CMakeLists.txt
Expand Up @@ -149,6 +149,7 @@ SET(QGIS_ANALYSIS_SRCS
vector/geometry_checker/qgsgeometryduplicatenodescheck.cpp
vector/geometry_checker/qgsgeometrygapcheck.cpp
vector/geometry_checker/qgsgeometryfollowboundariescheck.cpp
vector/geometry_checker/qgsgeometrymissingvertexcheck.cpp
vector/geometry_checker/qgsgeometryholecheck.cpp
vector/geometry_checker/qgsgeometrylineintersectioncheck.cpp
vector/geometry_checker/qgsgeometrylinelayerintersectioncheck.cpp
Expand Down Expand Up @@ -277,6 +278,7 @@ SET(QGIS_ANALYSIS_HDRS
vector/geometry_checker/qgsgeometryduplicatenodescheck.h
vector/geometry_checker/qgsgeometryfollowboundariescheck.h
vector/geometry_checker/qgsgeometrygapcheck.h
vector/geometry_checker/qgsgeometrymissingvertexcheck.h
vector/geometry_checker/qgsgeometryholecheck.h
vector/geometry_checker/qgsgeometrylineintersectioncheck.h
vector/geometry_checker/qgsgeometrylinelayerintersectioncheck.h
Expand Down
133 changes: 133 additions & 0 deletions src/analysis/vector/geometry_checker/qgsgeometrymissingvertexcheck.cpp
@@ -0,0 +1,133 @@
/***************************************************************************
qgsgeometrymissingvertexcheck.cpp
---------------------
begin : September 2018
copyright : (C) 2018 Matthias Kuhn
email : matthias@opengis.ch
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include "qgsgeometrymissingvertexcheck.h"

#include "qgsfeaturepool.h"
#include "qgsgeometrycollection.h"
#include "qgsmultipolygon.h"
#include "qgscurvepolygon.h"
#include "qgscurve.h"
#include "qgslinestring.h"
#include "qgsgeometryengine.h"
#include "qgsgeometryutils.h"

void QgsGeometryMissingVertexCheck::collectErrors( QList<QgsGeometryCheckError *> &errors, QStringList &messages, QAtomicInt *progressCounter, const QMap<QString, QgsFeatureIds> &ids ) const
{
Q_UNUSED( messages );
if ( progressCounter )
progressCounter->fetchAndAddRelaxed( 1 );

QMap<QString, QgsFeatureIds> featureIds = ids.isEmpty() ? allLayerFeatureIds() : ids;

QgsFeaturePool *featurePool = mContext->featurePools.value( featureIds.firstKey() );

const QgsGeometryCheckerUtils::LayerFeatures layerFeatures( mContext->featurePools, featureIds, mCompatibleGeometryTypes, nullptr, mContext, true );

for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeature : layerFeatures )
{
const QgsAbstractGeometry *geom = layerFeature.geometry().constGet();

if ( QgsCurvePolygon *polygon = qgsgeometry_cast<QgsCurvePolygon *>( geom ) )
{
processPolygon( polygon, featurePool, errors, layerFeature );
}
else if ( QgsGeometryCollection *collection = qgsgeometry_cast<QgsGeometryCollection *>( geom ) )
{
const int numGeometries = collection->numGeometries();
for ( int i = 0; i < numGeometries; ++i )
{
if ( QgsCurvePolygon *polygon = qgsgeometry_cast<QgsCurvePolygon *>( collection->geometryN( i ) ) )
{
processPolygon( polygon, featurePool, errors, layerFeature );
}
}
}
}
}

void QgsGeometryMissingVertexCheck::fixError( QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const
{
Q_UNUSED( mergeAttributeIndices );
Q_UNUSED( changes );
if ( method == NoChange )
{
error->setFixed( method );
}
}

QStringList QgsGeometryMissingVertexCheck::resolutionMethods() const
{
static QStringList methods = QStringList() << tr( "No action" );
return methods;
}

void QgsGeometryMissingVertexCheck::processPolygon( const QgsCurvePolygon *polygon, QgsFeaturePool *featurePool, QList<QgsGeometryCheckError *> &errors, const QgsGeometryCheckerUtils::LayerFeature &layerFeature ) const
{
const QgsFeature &currentFeature = layerFeature.feature();
std::unique_ptr<QgsMultiPolygon> boundaries = qgis::make_unique<QgsMultiPolygon>();

std::unique_ptr< QgsGeometryEngine > geomEngine = QgsGeometryCheckerUtils::createGeomEngine( polygon->exteriorRing(), mContext->tolerance );
boundaries->addGeometry( geomEngine->buffer( mContext->tolerance, 5 ) );

const int numRings = polygon->numInteriorRings();
for ( int i = 0; i < numRings; ++i )
{
geomEngine = QgsGeometryCheckerUtils::createGeomEngine( polygon->exteriorRing(), mContext->tolerance );
boundaries->addGeometry( geomEngine->buffer( mContext->tolerance, 5 ) );
}

geomEngine = QgsGeometryCheckerUtils::createGeomEngine( boundaries.get(), mContext->tolerance );
geomEngine->prepareGeometry();

const QgsFeatureIds fids = featurePool->getIntersects( boundaries->boundingBox() );

QgsFeature compareFeature;
for ( QgsFeatureId fid : fids )
{
if ( fid == currentFeature.id() )
continue;

if ( featurePool->getFeature( fid, compareFeature ) )
{
QgsVertexIterator vertexIterator = compareFeature.geometry().vertices();
while ( vertexIterator.hasNext() )
{
const QgsPoint &pt = vertexIterator.next();
if ( geomEngine->intersects( &pt ) )
{
QgsVertexId vertexId;
QgsPoint closestVertex = QgsGeometryUtils::closestVertex( *polygon, pt, vertexId );

if ( closestVertex.distance( pt ) > mContext->tolerance )
{
bool alreadyReported = false;
for ( QgsGeometryCheckError *error : qgis::as_const( errors ) )
{
// Only list missing vertices once
if ( error->featureId() == currentFeature.id() && error->location() == QgsPointXY( pt ) )
{
alreadyReported = true;
break;
}
}
if ( !alreadyReported )
errors.append( new QgsGeometryCheckError( this, layerFeature, QgsPointXY( pt ) ) );
}
}
}
}
}
}
@@ -0,0 +1,52 @@
/***************************************************************************
qgsgeometrymissingvertexcheck.h
---------------------
begin : September 2018
copyright : (C) 2018 Matthias Kuhn
email : matthias@opengis.ch
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#define SIP_NO_FILE

#ifndef QGSGEOMETRYMISSINGVERTEXCHECK_H
#define QGSGEOMETRYMISSINGVERTEXCHECK_H

#include "qgsgeometrycheck.h"

class QgsCurvePolygon;

/**
* \ingroup analysis
*
* A topology check for missing vertices.
* Any vertex which is on the border of another polygon but no corresponding vertex
* can be found on the other polygon will be reported as an error.
*
* \since QGIS 3.4
*/
class ANALYSIS_EXPORT QgsGeometryMissingVertexCheck : public QgsGeometryCheck
{
public:
explicit QgsGeometryMissingVertexCheck( QgsGeometryCheckerContext *context )
: QgsGeometryCheck( LayerCheck, {QgsWkbTypes::PolygonGeometry}, context )
{}
void collectErrors( QList<QgsGeometryCheckError *> &errors, QStringList &messages, QAtomicInt *progressCounter = nullptr, const QMap<QString, QgsFeatureIds> &ids = QMap<QString, QgsFeatureIds>() ) const override;
void fixError( QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override;
QStringList resolutionMethods() const override;
QString errorDescription() const override { return tr( "Missing Vertex" ); }
QString errorName() const override { return QStringLiteral( "QgsGeometryMissingVertexCheck" ); }

enum ResolutionMethod { NoChange };

private:
void processPolygon( const QgsCurvePolygon *polygon, QgsFeaturePool *featurePool, QList<QgsGeometryCheckError *> &errors, const QgsGeometryCheckerUtils::LayerFeature &layerFeature ) const;
};

#endif // QGSGEOMETRYMISSINGVERTEXCHECK_
29 changes: 29 additions & 0 deletions tests/src/geometry_checker/testqgsgeometrychecks.cpp
Expand Up @@ -28,6 +28,7 @@
#include "qgsgeometryfollowboundariescheck.h"
#include "qgsgeometrygapcheck.h"
#include "qgsgeometryholecheck.h"
#include "qgsgeometrymissingvertexcheck.h"
#include "qgsgeometrylineintersectioncheck.h"
#include "qgsgeometrylinelayerintersectioncheck.h"
#include "qgsgeometrymultipartcheck.h"
Expand Down Expand Up @@ -81,6 +82,7 @@ class TestQgsGeometryChecks: public QObject
void testDuplicateNodesCheck();
void testFollowBoundariesCheck();
void testGapCheck();
void testMissingVertexCheck();
void testHoleCheck();
void testLineIntersectionCheck();
void testLineLayerIntersectionCheck();
Expand Down Expand Up @@ -530,6 +532,33 @@ void TestQgsGeometryChecks::testGapCheck()
cleanupTestContext( context );
}

void TestQgsGeometryChecks::testMissingVertexCheck()
{
QTemporaryDir dir;
QMap<QString, QString> layers;
layers.insert( QStringLiteral( "missing_vertex.gpkg" ), QString() );
QgsGeometryCheckerContext *context = createTestContext( dir, layers );

// Test detection
QList<QgsGeometryCheckError *> checkErrors;
QStringList messages;

QgsGeometryMissingVertexCheck check( context );
check.collectErrors( checkErrors, messages );
listErrors( checkErrors, messages );

const QString layerId = layers.values().first();
QVERIFY( searchCheckErrors( checkErrors, layerId, 0, QgsPointXY( 0.251153, -0.460895 ), QgsVertexId() ).size() == 1 );
QVERIFY( searchCheckErrors( checkErrors, layerId, 3, QgsPointXY( 0.257985, -0.932886 ), QgsVertexId() ).size() == 1 );
QVERIFY( searchCheckErrors( checkErrors, layerId, 5, QgsPointXY( 0.59781, -0.480033 ), QgsVertexId() ).size() == 1 );
QVERIFY( searchCheckErrors( checkErrors, layerId, 5, QgsPointXY( 0.605252, -0.664875 ), QgsVertexId() ).size() == 1 );
QVERIFY( searchCheckErrors( checkErrors, layerId, 4, QgsPointXY( 0.259197, -0.478311 ), QgsVertexId() ).size() == 1 );

QCOMPARE( checkErrors.size(), 5 );

cleanupTestContext( context );
}

void TestQgsGeometryChecks::testHoleCheck()
{
QTemporaryDir dir;
Expand Down
Binary file not shown.

0 comments on commit 2275853

Please sign in to comment.