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