Skip to content

Commit 1372536

Browse files
committed
[Geometry checker] Add follow boundaries check
1 parent 1e49955 commit 1372536

17 files changed

+281
-48
lines changed

src/analysis/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ SET(QGIS_ANALYSIS_SRCS
9494
vector/geometry_checker/qgsgeometryduplicatecheck.cpp
9595
vector/geometry_checker/qgsgeometryduplicatenodescheck.cpp
9696
vector/geometry_checker/qgsgeometrygapcheck.cpp
97+
vector/geometry_checker/qgsgeometryfollowboundariescheck.cpp
9798
vector/geometry_checker/qgsgeometryholecheck.cpp
9899
vector/geometry_checker/qgsgeometrylineintersectioncheck.cpp
99100
vector/geometry_checker/qgsgeometrylinelayerintersectioncheck.cpp
@@ -128,6 +129,7 @@ SET(QGIS_ANALYSIS_MOC_HDRS
128129
vector/geometry_checker/qgsgeometrydegeneratepolygoncheck.h
129130
vector/geometry_checker/qgsgeometryduplicatecheck.h
130131
vector/geometry_checker/qgsgeometryduplicatenodescheck.h
132+
vector/geometry_checker/qgsgeometryfollowboundariescheck.h
131133
vector/geometry_checker/qgsgeometrygapcheck.h
132134
vector/geometry_checker/qgsgeometryholecheck.h
133135
vector/geometry_checker/qgsgeometrylineintersectioncheck.h
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/***************************************************************************
2+
qgsgeometryfollowboundariescheck.cpp
3+
---------------------
4+
begin : September 2017
5+
copyright : (C) 2017 by Sandro Mani / Sourcepole AG
6+
email : smani at sourcepole dot ch
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#include "qgscrscache.h"
17+
#include "qgsgeometryfollowboundariescheck.h"
18+
#include "qgsgeometryengine.h"
19+
#include "../qgsgeometrysnapper.h"
20+
21+
22+
QgsGeometryFollowBoundariesCheck::QgsGeometryFollowBoundariesCheck( QgsGeometryCheckerContext *context, QgsVectorLayer *checkLayer )
23+
: QgsGeometryCheck( FeatureNodeCheck, {QgsWkbTypes::PolygonGeometry}, context )
24+
{
25+
mCheckLayer = checkLayer;
26+
if ( mCheckLayer )
27+
{
28+
mIndex = new QgsSpatialIndex( *mCheckLayer->dataProvider() );
29+
}
30+
}
31+
32+
QgsGeometryFollowBoundariesCheck::~QgsGeometryFollowBoundariesCheck()
33+
{
34+
delete mIndex;
35+
}
36+
37+
void QgsGeometryFollowBoundariesCheck::collectErrors( QList<QgsGeometryCheckError *> &errors, QStringList &/*messages*/, QAtomicInt *progressCounter, const QMap<QString, QgsFeatureIds> &ids ) const
38+
{
39+
if ( !mIndex || !mCheckLayer )
40+
{
41+
return;
42+
}
43+
44+
QMap<QString, QgsFeatureIds> featureIds = ids.isEmpty() ? allLayerFeatureIds() : ids;
45+
featureIds.remove( mCheckLayer->id() ); // Don't check layer against itself
46+
QgsGeometryCheckerUtils::LayerFeatures layerFeatures( mContext->featurePools, featureIds, mCompatibleGeometryTypes, progressCounter );
47+
for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeature : layerFeatures )
48+
{
49+
const QgsAbstractGeometry *geom = layerFeature.geometry();
50+
51+
// The geometry to crs of the check layer
52+
QgsCoordinateTransform crst = QgsCoordinateTransformCache::instance()->transform( layerFeature.layer().crs().authid(), mCheckLayer->crs().authid() );
53+
QScopedPointer<QgsAbstractGeometry> geomt( geom->clone() );
54+
geomt->transform( crst );
55+
56+
QSharedPointer<QgsGeometryEngine> geomEngine = QgsGeometryCheckerUtils::createGeomEngine( geomt.data(), mContext->tolerance );
57+
58+
// Get potential reference features
59+
QgsRectangle searchBounds = geomt->boundingBox();
60+
searchBounds.grow( mContext->tolerance );
61+
QgsFeatureIds refFeatureIds = mIndex->intersects( searchBounds ).toSet();
62+
63+
QgsFeatureRequest refFeatureRequest = QgsFeatureRequest().setFilterFids( refFeatureIds ).setSubsetOfAttributes( QgsAttributeList() );
64+
QgsFeatureIterator refFeatureIt = mCheckLayer->getFeatures( refFeatureRequest );
65+
66+
if ( refFeatureIds.isEmpty() )
67+
{
68+
// If no potential reference features are found, the geometry is definitely not following boundaries of reference layer features
69+
errors.append( new QgsGeometryCheckError( this, layerFeature, QgsPointXY( geom->centroid() ) ) );
70+
}
71+
else
72+
{
73+
// All reference features must be either contained or disjoint from tested geometry
74+
QgsFeature refFeature;
75+
while ( refFeatureIt.nextFeature( refFeature ) )
76+
{
77+
QgsAbstractGeometry *refGeom = refFeature.geometry().geometry();
78+
QSharedPointer<QgsGeometryEngine> refgeomEngine = QgsGeometryCheckerUtils::createGeomEngine( refGeom, mContext->tolerance );
79+
QScopedPointer<QgsAbstractGeometry> reducedRefGeom( refgeomEngine->buffer( -mContext->tolerance, 0 ) );
80+
if ( !( geomEngine->contains( reducedRefGeom.data() ) || geomEngine->disjoint( reducedRefGeom.data() ) ) )
81+
{
82+
errors.append( new QgsGeometryCheckError( this, layerFeature, QgsPointXY( geom->centroid() ) ) );
83+
break;
84+
}
85+
}
86+
}
87+
}
88+
}
89+
90+
void QgsGeometryFollowBoundariesCheck::fixError( QgsGeometryCheckError *error, int method, const QMap<QString, int> & /*mergeAttributeIndices*/, Changes & /*changes*/ ) const
91+
{
92+
if ( method == NoChange )
93+
{
94+
error->setFixed( method );
95+
}
96+
else
97+
{
98+
error->setFixFailed( tr( "Unknown method" ) );
99+
}
100+
}
101+
102+
QStringList QgsGeometryFollowBoundariesCheck::getResolutionMethods() const
103+
{
104+
static QStringList methods = QStringList() << tr( "No action" );
105+
return methods;
106+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/***************************************************************************
2+
qgsgeometryfollowboundariescheck.h
3+
---------------------
4+
begin : September 2017
5+
copyright : (C) 2017 by Sandro Mani / Sourcepole AG
6+
email : smani at sourcepole dot ch
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#ifndef QGSGEOMETRYFOLLOWBOUNDARIESCHECK_H
17+
#define QGSGEOMETRYFOLLOWBOUNDARIESCHECK_H
18+
19+
#include "qgsgeometrycheck.h"
20+
21+
class QgsSpatialIndex;
22+
23+
24+
class ANALYSIS_EXPORT QgsGeometryFollowBoundariesCheck : public QgsGeometryCheck
25+
{
26+
Q_OBJECT
27+
28+
public:
29+
QgsGeometryFollowBoundariesCheck( QgsGeometryCheckerContext *context, QgsVectorLayer *checkLayer );
30+
~QgsGeometryFollowBoundariesCheck();
31+
void collectErrors( QList<QgsGeometryCheckError *> &errors, QStringList &messages, QAtomicInt *progressCounter = nullptr, const QMap<QString, QgsFeatureIds> &ids = QMap<QString, QgsFeatureIds>() ) const override;
32+
void fixError( QgsGeometryCheckError *error, int method, const QMap<QString, int> &mergeAttributeIndices, Changes &changes ) const override;
33+
QStringList getResolutionMethods() const override;
34+
QString errorDescription() const override { return tr( "Polygon does not follow boundaries" ); }
35+
QString errorName() const override { return QStringLiteral( "QgsGeometryFollowBoundariesCheck" ); }
36+
private:
37+
enum ResolutionMethod { NoChange };
38+
QgsVectorLayer *mCheckLayer;
39+
QgsSpatialIndex *mIndex = nullptr;
40+
};
41+
42+
#endif // QGSGEOMETRYFOLLOWBOUNDARIESCHECK_H

src/plugins/geometry_checker/qgsgeometrycheckersetuptab.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ QgsGeometryCheckerSetupTab::QgsGeometryCheckerSetupTab( QgisInterface *iface, QD
7979
connect( ui.checkBoxSliverPolygons, &QAbstractButton::toggled, ui.widgetSliverThreshold, &QWidget::setEnabled );
8080
connect( ui.checkBoxSliverArea, &QAbstractButton::toggled, ui.doubleSpinBoxSliverArea, &QWidget::setEnabled );
8181
connect( ui.checkLineLayerIntersection, &QAbstractButton::toggled, ui.comboLineLayerIntersection, &QComboBox::setEnabled );
82+
connect( ui.checkBoxFollowBoundaries, &QAbstractButton::toggled, ui.comboBoxFollowBoundaries, &QComboBox::setEnabled );
8283

8384
for ( const QgsGeometryCheckFactory *factory : QgsGeometryCheckFactoryRegistry::getCheckFactories() )
8485
{
@@ -105,6 +106,7 @@ void QgsGeometryCheckerSetupTab::updateLayers()
105106
}
106107
ui.listWidgetInputLayers->clear();
107108
ui.comboLineLayerIntersection->clear();
109+
ui.comboBoxFollowBoundaries->clear();
108110

109111
// Collect layers
110112
for ( QgsVectorLayer *layer : QgsProject::instance()->layers<QgsVectorLayer *>() )
@@ -124,6 +126,7 @@ void QgsGeometryCheckerSetupTab::updateLayers()
124126
{
125127
item->setIcon( QgsApplication::getThemeIcon( "/mIconPolygonLayer.svg" ) );
126128
ui.comboLineLayerIntersection->addItem( layer->name(), layer->id() );
129+
ui.comboBoxFollowBoundaries->addItem( layer->name(), layer->id() );
127130
}
128131
else
129132
{
@@ -264,6 +267,13 @@ void QgsGeometryCheckerSetupTab::runChecks()
264267
}
265268
}
266269
}
270+
QgsVectorLayer *lineLayerCheckLayer = ui.comboLineLayerIntersection->isEnabled() ? dynamic_cast<QgsVectorLayer *>( QgsProject::instance()->mapLayer( ui.comboLineLayerIntersection->currentData().toString() ) ) : nullptr;
271+
QgsVectorLayer *followBoundaryCheckLayer = ui.comboBoxFollowBoundaries->isEnabled() ? dynamic_cast<QgsVectorLayer *>( QgsProject::instance()->mapLayer( ui.comboBoxFollowBoundaries->currentData().toString() ) ) : nullptr;
272+
if ( layers.contains( lineLayerCheckLayer ) || layers.contains( followBoundaryCheckLayer ) )
273+
{
274+
QMessageBox::critical( this, tr( "Error" ), tr( "The test layer set contains a layer selected for a topology check." ) );
275+
return;
276+
}
267277

268278
for ( QgsVectorLayer *layer : layers )
269279
{

src/plugins/geometry_checker/qgsgeometrycheckersetuptab.ui

Lines changed: 62 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@
4141
<property name="geometry">
4242
<rect>
4343
<x>0</x>
44-
<y>0</y>
44+
<y>-265</y>
4545
<width>626</width>
46-
<height>991</height>
46+
<height>1016</height>
4747
</rect>
4848
</property>
4949
<layout class="QGridLayout" name="gridLayout_4">
@@ -485,29 +485,27 @@
485485
<property name="spacing">
486486
<number>2</number>
487487
</property>
488-
<item row="3" column="0">
489-
<widget class="QCheckBox" name="checkBoxGaps">
490-
<property name="sizePolicy">
491-
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
492-
<horstretch>0</horstretch>
493-
<verstretch>0</verstretch>
494-
</sizepolicy>
495-
</property>
488+
<item row="0" column="0">
489+
<widget class="QCheckBox" name="checkBoxDuplicates">
496490
<property name="text">
497-
<string>Check for gaps smaller than (map units sqr.)</string>
491+
<string>Check for duplicates</string>
498492
</property>
499493
</widget>
500494
</item>
501-
<item row="2" column="0">
502-
<widget class="QCheckBox" name="checkBoxOverlaps">
503-
<property name="sizePolicy">
504-
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
505-
<horstretch>0</horstretch>
506-
<verstretch>0</verstretch>
507-
</sizepolicy>
495+
<item row="3" column="1">
496+
<widget class="QDoubleSpinBox" name="doubleSpinBoxGapArea">
497+
<property name="decimals">
498+
<number>6</number>
508499
</property>
509-
<property name="text">
510-
<string>Check for overlaps smaller than (map units sqr.)</string>
500+
<property name="maximum">
501+
<double>999999999.000000000000000</double>
502+
</property>
503+
</widget>
504+
</item>
505+
<item row="7" column="1">
506+
<widget class="QComboBox" name="comboLineLayerIntersection">
507+
<property name="enabled">
508+
<bool>false</bool>
511509
</property>
512510
</widget>
513511
</item>
@@ -518,22 +516,21 @@
518516
</property>
519517
</widget>
520518
</item>
521-
<item row="0" column="0">
522-
<widget class="QCheckBox" name="checkBoxDuplicates">
523-
<property name="text">
524-
<string>Check for duplicates</string>
519+
<item row="3" column="0">
520+
<widget class="QCheckBox" name="checkBoxGaps">
521+
<property name="sizePolicy">
522+
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
523+
<horstretch>0</horstretch>
524+
<verstretch>0</verstretch>
525+
</sizepolicy>
525526
</property>
526-
</widget>
527-
</item>
528-
<item row="1" column="0">
529-
<widget class="QCheckBox" name="checkBoxCovered">
530527
<property name="text">
531-
<string>Check for features within other features</string>
528+
<string>Check for gaps smaller than (map units sqr.)</string>
532529
</property>
533530
</widget>
534531
</item>
535-
<item row="3" column="1">
536-
<widget class="QDoubleSpinBox" name="doubleSpinBoxGapArea">
532+
<item row="2" column="1">
533+
<widget class="QDoubleSpinBox" name="doubleSpinBoxOverlapArea">
537534
<property name="decimals">
538535
<number>6</number>
539536
</property>
@@ -542,27 +539,37 @@
542539
</property>
543540
</widget>
544541
</item>
545-
<item row="9" column="0" colspan="2">
546-
<widget class="QLabel" name="label">
542+
<item row="1" column="0">
543+
<widget class="QCheckBox" name="checkBoxCovered">
547544
<property name="text">
548-
<string>&lt;i&gt;Note: Topology checks are performed in the current map CRS.&lt;/i&gt;</string>
545+
<string>Check for features within other features</string>
549546
</property>
550547
</widget>
551548
</item>
552-
<item row="2" column="1">
553-
<widget class="QDoubleSpinBox" name="doubleSpinBoxOverlapArea">
554-
<property name="decimals">
555-
<number>6</number>
549+
<item row="4" column="0">
550+
<widget class="QCheckBox" name="checkPointCoveredByLine">
551+
<property name="text">
552+
<string>Points must be covered by lines</string>
556553
</property>
557-
<property name="maximum">
558-
<double>999999999.000000000000000</double>
554+
</widget>
555+
</item>
556+
<item row="7" column="0">
557+
<widget class="QCheckBox" name="checkLineLayerIntersection">
558+
<property name="text">
559+
<string>Lines must not intersect with features of layer</string>
559560
</property>
560561
</widget>
561562
</item>
562-
<item row="4" column="0">
563-
<widget class="QCheckBox" name="checkPointCoveredByLine">
563+
<item row="2" column="0">
564+
<widget class="QCheckBox" name="checkBoxOverlaps">
565+
<property name="sizePolicy">
566+
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
567+
<horstretch>0</horstretch>
568+
<verstretch>0</verstretch>
569+
</sizepolicy>
570+
</property>
564571
<property name="text">
565-
<string>Points must be covered by lines</string>
572+
<string>Check for overlaps smaller than (map units sqr.)</string>
566573
</property>
567574
</widget>
568575
</item>
@@ -573,15 +580,22 @@
573580
</property>
574581
</widget>
575582
</item>
576-
<item row="7" column="0">
577-
<widget class="QCheckBox" name="checkLineLayerIntersection">
583+
<item row="10" column="0" colspan="2">
584+
<widget class="QLabel" name="label">
578585
<property name="text">
579-
<string>Lines must not intersect with features of layer</string>
586+
<string>&lt;i&gt;Note: Topology checks are performed in the current map CRS.&lt;/i&gt;</string>
580587
</property>
581588
</widget>
582589
</item>
583-
<item row="7" column="1">
584-
<widget class="QComboBox" name="comboLineLayerIntersection">
590+
<item row="8" column="0">
591+
<widget class="QCheckBox" name="checkBoxFollowBoundaries">
592+
<property name="text">
593+
<string>Polygons must follow boundaries of layer</string>
594+
</property>
595+
</widget>
596+
</item>
597+
<item row="8" column="1">
598+
<widget class="QComboBox" name="comboBoxFollowBoundaries">
585599
<property name="enabled">
586600
<bool>false</bool>
587601
</property>

0 commit comments

Comments
 (0)