QGIS API Documentation 3.28.14-Firenze (exported)
Loading...
Searching...
No Matches
qgsgeometryvalidator.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsgeometryvalidator.cpp - geometry validation thread
3 -------------------------------------------------------------------
4Date : 03.01.2012
5Copyright : (C) 2012 by Juergen E. Fischer
6email : jef at norbit dot de
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 "qgis.h"
18#include "qgsgeometry.h"
19#include "qgslogger.h"
20#include "qgsgeos.h"
22#include "qgspolygon.h"
23#include "qgscurvepolygon.h"
24#include "qgscurve.h"
25#include "qgsvertexid.h"
26
27QgsGeometryValidator::QgsGeometryValidator( const QgsGeometry &geometry, QVector<QgsGeometry::Error> *errors, Qgis::GeometryValidationEngine method )
28 : mGeometry( geometry )
29 , mErrors( errors )
30 , mStop( false )
31 , mErrorCount( 0 )
32 , mMethod( method )
33{
34}
35
41
43{
44 mStop = true;
45}
46
47void QgsGeometryValidator::checkRingIntersections( int partIndex0, int ringIndex0, const QgsCurve *ring0, int partIndex1, int ringIndex1, const QgsCurve *ring1 )
48{
49 const QgsLineString *ringLine0 = qgsgeometry_cast< const QgsLineString * >( ring0 );
50 std::unique_ptr< QgsLineString > segmentisedRing0;
51 if ( !ringLine0 )
52 {
53 segmentisedRing0.reset( qgsgeometry_cast< QgsLineString * >( ring0->segmentize() ) );
54 ringLine0 = segmentisedRing0.get();
55 }
56
57 const QgsLineString *ringLine1 = qgsgeometry_cast< const QgsLineString * >( ring1 );
58 std::unique_ptr< QgsLineString > segmentisedRing1;
59 if ( !ringLine1 )
60 {
61 segmentisedRing1.reset( qgsgeometry_cast< QgsLineString * >( ring1->segmentize() ) );
62 ringLine1 = segmentisedRing1.get();
63 }
64
65 Q_ASSERT( ringLine0 );
66 Q_ASSERT( ringLine1 );
67
68 for ( int i = 0; !mStop && i < ringLine0->numPoints() - 1; i++ )
69 {
70 const double ring0XAti = ringLine0->xAt( i );
71 const double ring0YAti = ringLine0->yAt( i );
72 const QgsVector v( ringLine0->xAt( i + 1 ) - ring0XAti, ringLine0->yAt( i + 1 ) - ring0YAti );
73
74 for ( int j = 0; !mStop && j < ringLine1->numPoints() - 1; j++ )
75 {
76 const double ring1XAtj = ringLine1->xAt( j );
77 const double ring1YAtj = ringLine1->yAt( j );
78 const QgsVector w( ringLine1->xAt( j + 1 ) - ring1XAtj, ringLine1->yAt( j + 1 ) - ring1YAtj );
79
80 double sX;
81 double sY;
82 if ( intersectLines( ring0XAti, ring0YAti, v, ring1XAtj, ring1YAtj, w, sX, sY ) )
83 {
84 double d = -distLine2Point( ring0XAti, ring0YAti, v.perpVector(), sX, sY );
85
86 if ( d >= 0 && d <= v.length() )
87 {
88 d = -distLine2Point( ring1XAtj, ring1YAtj, w.perpVector(), sX, sY );
89 if ( d > 0 && d < w.length() &&
90 ringLine0->pointN( i + 1 ) != ringLine1->pointN( j + 1 ) && ringLine0->pointN( i + 1 ) != ringLine1->pointN( j ) &&
91 ringLine0->pointN( i + 0 ) != ringLine1->pointN( j + 1 ) && ringLine0->pointN( i + 0 ) != ringLine1->pointN( j ) )
92 {
93 const QString msg = QObject::tr( "segment %1 of ring %2 of polygon %3 intersects segment %4 of ring %5 of polygon %6 at %7, %8" )
94 .arg( i ).arg( ringIndex0 ).arg( partIndex0 )
95 .arg( j ).arg( ringIndex1 ).arg( partIndex1 )
96 .arg( sX ).arg( sY );
97 emit errorFound( QgsGeometry::Error( msg, QgsPointXY( sX, sY ) ) );
98 mErrorCount++;
99 }
100 }
101 }
102 }
103 }
104}
105
106void QgsGeometryValidator::validatePolyline( int i, const QgsLineString *line, bool ring )
107{
108 if ( !line )
109 return;
110
111 if ( ring )
112 {
113 if ( line->numPoints() < 4 )
114 {
115 const QString msg = QObject::tr( "ring %1 with less than four points" ).arg( i );
116 QgsDebugMsgLevel( msg, 2 );
117 emit errorFound( QgsGeometry::Error( msg ) );
118 mErrorCount++;
119 return;
120 }
121
122 if ( !line->isClosed() )
123 {
124 const QgsPoint startPoint = line->startPoint();
125 const QgsPoint endPoint = line->endPoint();
126 QString msg;
127 if ( line->is3D() && line->isClosed2D() )
128 {
129 msg = QObject::tr( "ring %1 not closed, Z mismatch: %2 vs %3" ).arg( i ).arg( startPoint.z() ).arg( endPoint.z() );
130 }
131 else
132 {
133 msg = QObject::tr( "ring %1 not closed" ).arg( i );
134 QgsDebugMsgLevel( msg, 2 );
135 }
136 emit errorFound( QgsGeometry::Error( msg, QgsPointXY( startPoint.x(), startPoint.y() ) ) );
137 mErrorCount++;
138 return;
139 }
140 }
141 else if ( line->numPoints() < 2 )
142 {
143 const QString msg = QObject::tr( "line %1 with less than two points" ).arg( i );
144 QgsDebugMsgLevel( msg, 2 );
145 emit errorFound( QgsGeometry::Error( msg ) );
146 mErrorCount++;
147 return;
148 }
149
150 std::unique_ptr< QgsLineString > noDupes;
151
152 // test for duplicate nodes, and if we find any flag errors and then remove them so that the subsequent
153 // tests work OK.
154 const QVector< QgsVertexId > duplicateNodes = line->collectDuplicateNodes( 1E-8 );
155 if ( !duplicateNodes.empty() )
156 {
157 noDupes.reset( line->clone() );
158 for ( int j = duplicateNodes.size() - 1; j >= 0; j-- )
159 {
160 const QgsVertexId duplicateVertex = duplicateNodes.at( j );
161 const QgsPointXY duplicationLocation = noDupes->vertexAt( duplicateVertex );
162 noDupes->deleteVertex( duplicateVertex );
163 int n = 1;
164
165 // count how many other points exist at this location too
166 for ( int k = j - 1; k >= 0; k-- )
167 {
168 const QgsVertexId prevDupe = duplicateNodes.at( k );
169 const QgsPoint prevPoint = noDupes->vertexAt( prevDupe );
170 if ( qgsDoubleNear( duplicationLocation.x(), prevPoint.x(), 1E-8 ) && qgsDoubleNear( duplicationLocation.y(), prevPoint.y(), 1E-8 ) )
171 {
172 noDupes->deleteVertex( prevDupe );
173 n++;
174 }
175 else
176 {
177 break;
178 }
179 }
180
181 j -= n - 1;
182
183 const QString msg = QObject::tr( "line %1 contains %n duplicate node(s) starting at vertex %2", "number of duplicate nodes", n + 1 ).arg( i + 1 ).arg( duplicateVertex.vertex - n + 1 );
184 QgsDebugMsgLevel( msg, 2 );
185 emit errorFound( QgsGeometry::Error( msg, duplicationLocation ) );
186 mErrorCount++;
187 }
188 line = noDupes.get();
189 }
190
191 for ( int j = 0; !mStop && j < line->numPoints() - 3; j++ )
192 {
193 const double xAtJ = line->xAt( j );
194 const double yAtJ = line->yAt( j );
195 const QgsVector v( line->xAt( j + 1 ) - xAtJ, line->yAt( j + 1 ) - yAtJ );
196 const double vl = v.length();
197
198 const int n = ( j == 0 && ring ) ? line->numPoints() - 2 : line->numPoints() - 1;
199
200 for ( int k = j + 2; !mStop && k < n; k++ )
201 {
202 const double xAtK = line->xAt( k );
203 const double yAtK = line->yAt( k );
204
205 const QgsVector w( line->xAt( k + 1 ) - xAtK, line->yAt( k + 1 ) - yAtK );
206
207 double sX;
208 double sY;
209 if ( !intersectLines( xAtJ, yAtJ, v, xAtK, yAtK, w, sX, sY ) )
210 continue;
211
212 double d = 0.0;
213 try
214 {
215 d = -distLine2Point( xAtJ, yAtJ, v.perpVector(), sX, sY );
216 }
217 catch ( QgsException &e )
218 {
219 Q_UNUSED( e )
220 QgsDebugMsg( "Error validating: " + e.what() );
221 continue;
222 }
223 if ( d < 0 || d > vl )
224 continue;
225
226 try
227 {
228 d = -distLine2Point( xAtK, yAtK, w.perpVector(), sX, sY );
229 }
230 catch ( QgsException &e )
231 {
232 Q_UNUSED( e )
233 QgsDebugMsg( "Error validating: " + e.what() );
234 continue;
235 }
236
237 if ( d <= 0 || d >= w.length() )
238 continue;
239
240 const QString msg = QObject::tr( "segments %1 and %2 of line %3 intersect at %4, %5" ).arg( j ).arg( k ).arg( i ).arg( sX ).arg( sY );
241 QgsDebugMsgLevel( msg, 2 );
242 emit errorFound( QgsGeometry::Error( msg, QgsPointXY( sX, sY ) ) );
243 mErrorCount++;
244 }
245 }
246}
247
248void QgsGeometryValidator::validatePolygon( int partIndex, const QgsCurvePolygon *polygon )
249{
250 // check if holes are inside polygon
251 for ( int i = 0; !mStop && i < polygon->numInteriorRings(); ++i )
252 {
253 if ( !ringInRing( polygon->interiorRing( i ), polygon->exteriorRing() ) )
254 {
255 const QString msg = QObject::tr( "ring %1 of polygon %2 not in exterior ring" ).arg( i + 1 ).arg( partIndex );
256 QgsDebugMsg( msg );
257 emit errorFound( QgsGeometry::Error( msg ) );
258 mErrorCount++;
259 }
260 }
261
262 // check holes for intersections
263 for ( int i = 0; !mStop && i < polygon->numInteriorRings(); i++ )
264 {
265 for ( int j = i + 1; !mStop && j < polygon->numInteriorRings(); j++ )
266 {
267 checkRingIntersections( partIndex, i + 1, polygon->interiorRing( i ),
268 partIndex, j + 1, polygon->interiorRing( j ) );
269 }
270 }
271
272 // check if rings are self-intersecting
273 validatePolyline( 0, qgsgeometry_cast< const QgsLineString * >( polygon->exteriorRing() ), true );
274 for ( int i = 0; !mStop && i < polygon->numInteriorRings(); i++ )
275 {
276 validatePolyline( i + 1, qgsgeometry_cast< const QgsLineString * >( polygon->interiorRing( i ) ), true );
277 }
278}
279
281{
282 mErrorCount = 0;
283 if ( mGeometry.isNull() )
284 {
285 return;
286 }
287
288 switch ( mMethod )
289 {
291 {
292 // avoid calling geos for trivial point geometries
294 {
295 return;
296 }
297
298 const QgsGeos geos( mGeometry.constGet() );
299 QString error;
300 QgsGeometry errorLoc;
301 if ( !geos.isValid( &error, true, &errorLoc ) )
302 {
303 if ( errorLoc.isNull() )
304 {
305 emit errorFound( QgsGeometry::Error( error ) );
306 mErrorCount++;
307 }
308 else
309 {
310 const QgsPointXY point = errorLoc.asPoint();
311 emit errorFound( QgsGeometry::Error( error, point ) );
312 mErrorCount++;
313 }
314 }
315
316 break;
317 }
318
320 {
321 switch ( QgsWkbTypes::flatType( mGeometry.constGet()->wkbType() ) )
322 {
325 break;
326
328 validatePolyline( 0, qgsgeometry_cast< const QgsLineString * >( mGeometry.constGet() ) );
329 break;
330
332 {
333 const QgsGeometryCollection *collection = qgsgeometry_cast< const QgsGeometryCollection * >( mGeometry.constGet() );
334 for ( int i = 0; !mStop && i < collection->numGeometries(); i++ )
335 validatePolyline( i, qgsgeometry_cast< const QgsLineString * >( collection->geometryN( i ) ) );
336 break;
337 }
338
341 validatePolygon( 0, qgsgeometry_cast< const QgsCurvePolygon * >( mGeometry.constGet() ) );
342 break;
343
346 {
347 const QgsGeometryCollection *collection = qgsgeometry_cast< const QgsGeometryCollection * >( mGeometry.constGet() );
348 for ( int i = 0; !mStop && i < collection->numGeometries(); i++ )
349 validatePolygon( i, qgsgeometry_cast< const QgsCurvePolygon * >( collection->geometryN( i ) ) );
350
351 for ( int i = 0; !mStop && i < collection->numGeometries(); i++ )
352 {
353 const QgsCurvePolygon *poly = qgsgeometry_cast< const QgsCurvePolygon * >( collection->geometryN( i ) );
354 if ( !poly->exteriorRing() || poly->exteriorRing()->isEmpty() )
355 {
356 emit errorFound( QgsGeometry::Error( QObject::tr( "Polygon %1 has no rings" ).arg( i ) ) );
357 mErrorCount++;
358 continue;
359 }
360
361 for ( int j = i + 1; !mStop && j < collection->numGeometries(); j++ )
362 {
363 const QgsCurvePolygon *poly2 = qgsgeometry_cast< const QgsCurvePolygon * >( collection->geometryN( j ) );
364 if ( !poly2->exteriorRing() || poly2->exteriorRing()->isEmpty() )
365 continue;
366
367 if ( ringInRing( poly->exteriorRing(),
368 poly2->exteriorRing() ) )
369 {
370 emit errorFound( QgsGeometry::Error( QObject::tr( "Polygon %1 lies inside polygon %2" ).arg( i ).arg( j ) ) );
371 mErrorCount++;
372 }
373 else if ( ringInRing( poly2->exteriorRing(),
374 poly->exteriorRing() ) )
375 {
376 emit errorFound( QgsGeometry::Error( QObject::tr( "Polygon %1 lies inside polygon %2" ).arg( j ).arg( i ) ) );
377 mErrorCount++;
378 }
379 else
380 {
381 checkRingIntersections( i, 0, poly->exteriorRing(),
382 j, 0, poly2->exteriorRing() );
383 }
384 }
385 }
386 break;
387 }
388
390 {
391 emit errorFound( QgsGeometry::Error( QObject::tr( "Unknown geometry type %1" ).arg( mGeometry.wkbType() ) ) );
392 mErrorCount++;
393 break;
394 }
395
396 default:
397 break;
398 }
399
400 if ( mStop )
401 {
402 emit validationFinished( QObject::tr( "Geometry validation was aborted." ) );
403 }
404 else if ( mErrorCount > 0 )
405 {
406 emit validationFinished( QObject::tr( "Geometry has %n error(s).", nullptr, mErrorCount ) );
407 }
408 else
409 {
410 emit validationFinished( QObject::tr( "Geometry is valid." ) );
411 }
412 break;
413 }
414 }
415}
416
418{
419 if ( mErrors )
420 *mErrors << e;
421}
422
423void QgsGeometryValidator::validateGeometry( const QgsGeometry &geometry, QVector<QgsGeometry::Error> &errors, Qgis::GeometryValidationEngine method )
424{
425 QgsGeometryValidator *gv = new QgsGeometryValidator( geometry, &errors, method );
427 gv->run();
428 gv->wait();
429}
430
431//
432// distance of point q from line through p in direction v
433// return >0 => q lies left of the line
434// <0 => q lies right of the line
435//
436double QgsGeometryValidator::distLine2Point( double px, double py, QgsVector v, double qX, double qY )
437{
438 const double l = v.length();
439 if ( qgsDoubleNear( l, 0 ) )
440 {
441 throw QgsException( QObject::tr( "invalid line" ) );
442 }
443
444 return ( v.x() * ( qY - py ) - v.y() * ( qX - px ) ) / l;
445}
446
447bool QgsGeometryValidator::intersectLines( double px, double py, QgsVector v, double qx, double qy, QgsVector w, double &sX, double &sY )
448{
449 const double d = v.y() * w.x() - v.x() * w.y();
450
451 if ( qgsDoubleNear( d, 0 ) )
452 return false;
453
454 const double dx = qx - px;
455 const double dy = qy - py;
456 const double k = ( dy * w.x() - dx * w.y() ) / d;
457
458 sX = px + v.x() * k;
459 sY = py + v.y() * k;
460
461 return true;
462}
463
464bool QgsGeometryValidator::pointInRing( const QgsCurve *ring, double pX, double pY )
465{
466 if ( !ring->boundingBox().contains( pX, pY ) )
467 return false;
468
469 bool inside = false;
470 int j = ring->numPoints() - 1;
471
472 for ( int i = 0; !mStop && i < ring->numPoints(); i++ )
473 {
474 const double xAti = ring->xAt( i );
475 const double yAti = ring->yAt( i );
476 const double xAtj = ring->xAt( j );
477 const double yAtj = ring->yAt( j );
478
479 if ( qgsDoubleNear( xAti, pX ) && qgsDoubleNear( yAti, pY ) )
480 return true;
481
482 if ( ( yAti < pY && yAtj >= pY ) ||
483 ( yAtj < pY && yAti >= pY ) )
484 {
485 if ( xAti + ( pY - yAti ) / ( yAtj - yAti ) * ( xAtj - xAti ) <= pX )
486 inside = !inside;
487 }
488
489 j = i;
490 }
491
492 return inside;
493}
494
495bool QgsGeometryValidator::ringInRing( const QgsCurve *inside, const QgsCurve *outside )
496{
497 if ( !outside->boundingBox().contains( inside->boundingBox() ) )
498 return false;
499
500 for ( int i = 0; !mStop && i < inside->numPoints(); i++ )
501 {
502 if ( !pointInRing( outside, inside->xAt( i ), inside->yAt( i ) ) )
503 return false;
504 }
505
506 return true;
507}
GeometryValidationEngine
Available engines for validating geometries.
Definition qgis.h:996
@ QgisInternal
Use internal QgsGeometryValidator method.
@ Geos
Use GEOS validation methods.
bool is3D() const
Returns true if the geometry is 3D and contains a z-value.
QgsWkbTypes::Type wkbType() const
Returns the WKB type of the geometry.
virtual bool isEmpty() const
Returns true if the geometry is empty.
Curve polygon geometry type.
int numInteriorRings() const
Returns the number of interior rings contained with the curve polygon.
const QgsCurve * exteriorRing() const
Returns the curve polygon's exterior ring.
const QgsCurve * interiorRing(int i) const
Retrieves an interior ring from the curve polygon.
Abstract base class for curved geometry type.
Definition qgscurve.h:36
QgsRectangle boundingBox() const override
Returns the minimal bounding box for the geometry.
Definition qgscurve.cpp:238
virtual int numPoints() const =0
Returns the number of points in the curve.
QgsCurve * segmentize(double tolerance=M_PI_2/90, SegmentationToleranceType toleranceType=MaximumAngle) const override
Returns a geometry without curves.
Definition qgscurve.cpp:175
virtual double xAt(int index) const =0
Returns the x-coordinate of the specified node in the line string.
virtual double yAt(int index) const =0
Returns the y-coordinate of the specified node in the line string.
Defines a QGIS exception class.
QString what() const
int numGeometries() const
Returns the number of geometries within the collection.
const QgsAbstractGeometry * geometryN(int n) const
Returns a const reference to a geometry from within the collection.
void validationFinished(const QString &summary)
Sent when the validation is finished.
void errorFound(const QgsGeometry::Error &error)
Sent when an error has been found during the validation process.
QgsGeometryValidator(const QgsGeometry &geometry, QVector< QgsGeometry::Error > *errors=nullptr, Qgis::GeometryValidationEngine method=Qgis::GeometryValidationEngine::QgisInternal)
Constructor for QgsGeometryValidator.
static void validateGeometry(const QgsGeometry &geometry, QVector< QgsGeometry::Error > &errors, Qgis::GeometryValidationEngine method=Qgis::GeometryValidationEngine::QgisInternal)
Validate geometry and produce a list of geometry errors.
void addError(const QgsGeometry::Error &)
A geometry error.
A geometry is the spatial representation of a feature.
QgsWkbTypes::Type wkbType() const
Returns type of the geometry as a WKB type (point / linestring / polygon etc.)
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
QgsPointXY asPoint() const
Returns the contents of the geometry as a 2-dimensional point.
Does vector analysis using the geos library and handles import, export, exception handling*.
Definition qgsgeos.h:99
Line string geometry type, with support for z-dimension and m-values.
bool isClosed() const override
Returns true if the curve is closed.
QgsPoint startPoint() const override
Returns the starting point of the curve.
int numPoints() const override
Returns the number of points in the curve.
QgsPoint pointN(int i) const
Returns the specified point from inside the line string.
double yAt(int index) const override
Returns the y-coordinate of the specified node in the line string.
QgsPoint endPoint() const override
Returns the end point of the curve.
QVector< QgsVertexId > collectDuplicateNodes(double epsilon=4 *std::numeric_limits< double >::epsilon(), bool useZValues=false) const
Returns a list of any duplicate nodes contained in the geometry, within the specified tolerance.
QgsLineString * clone() const override
Clones the geometry by performing a deep copy.
bool isClosed2D() const override
Returns true if the curve is closed.
double xAt(int index) const override
Returns the x-coordinate of the specified node in the line string.
A class to represent a 2D point.
Definition qgspointxy.h:59
double y
Definition qgspointxy.h:63
double x
Definition qgspointxy.h:62
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
QgsPoint vertexAt(QgsVertexId) const override
Returns the point corresponding to a specified vertex id.
Definition qgspoint.cpp:525
bool deleteVertex(QgsVertexId position) override
Deletes a vertex within the geometry.
Definition qgspoint.cpp:459
double z
Definition qgspoint.h:54
double x
Definition qgspoint.h:52
double y
Definition qgspoint.h:53
bool contains(const QgsRectangle &rect) const
Returns true when rectangle contains other rectangle.
A class to represent a vector.
Definition qgsvector.h:30
double y() const
Returns the vector's y-component.
Definition qgsvector.h:156
double x() const
Returns the vector's x-component.
Definition qgsvector.h:147
double length() const
Returns the length of the vector.
Definition qgsvector.h:128
static GeometryType geometryType(Type type)
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
static Type flatType(Type type)
Returns the flat type for a WKB type.
Contains geos related utilities and functions.
Definition qgsgeos.h:37
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:2527
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:39
#define QgsDebugMsg(str)
Definition qgslogger.h:38
Utility class for identifying a unique vertex within a geometry.
Definition qgsvertexid.h:31
int vertex
Vertex number.
Definition qgsvertexid.h:95