QGIS API Documentation 3.99.0-Master (2fe06baccd8)
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
17
18#include "qgis.h"
19#include "qgscurve.h"
20#include "qgscurvepolygon.h"
21#include "qgsgeometry.h"
23#include "qgsgeos.h"
24#include "qgslogger.h"
25#include "qgsvertexid.h"
26
27#include "moc_qgsgeometryvalidator.cpp"
28
29QgsGeometryValidator::QgsGeometryValidator( const QgsGeometry &geometry, QVector<QgsGeometry::Error> *errors, Qgis::GeometryValidationEngine method )
30 : mGeometry( geometry )
31 , mErrors( errors )
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{
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 // segment Intersection
192 for ( int j = 0; !mStop && j < line->numPoints() - 3; j++ )
193 {
194 const int n = ( j == 0 && ring ) ? line->numPoints() - 2 : line->numPoints() - 1;
195 for ( int k = j + 2; !mStop && k < n; k++ )
196 {
197 double intersectionPointX = std::numeric_limits<double>::quiet_NaN();
198 double intersectionPointY = std::numeric_limits<double>::quiet_NaN();
199 bool isIntersection = false;
200 if ( QgsGeometryUtilsBase::segmentIntersection( line->xAt( j ), line->yAt( j ), line->xAt( j + 1 ), line->yAt( j + 1 ), line->xAt( k ), line->yAt( k ), line->xAt( k + 1 ), line->yAt( k + 1 ), intersectionPointX, intersectionPointY, isIntersection ) )
201 {
202 const QString msg = QObject::tr( "segments %1 and %2 of line %3 intersect at %4, %5" ).arg( j ).arg( k ).arg( i ).arg( intersectionPointX ).arg( intersectionPointY );
203 QgsDebugMsgLevel( msg, 2 );
204 emit errorFound( QgsGeometry::Error( msg, QgsPointXY( intersectionPointX, intersectionPointY ) ) );
205 mErrorCount++;
206 }
207 }
208 }
209}
210
211void QgsGeometryValidator::validatePolygon( int partIndex, const QgsCurvePolygon *polygon )
212{
213 // check if holes are inside polygon
214 for ( int i = 0; !mStop && i < polygon->numInteriorRings(); ++i )
215 {
216 if ( !ringInRing( polygon->interiorRing( i ), polygon->exteriorRing() ) )
217 {
218 const QString msg = QObject::tr( "ring %1 of polygon %2 not in exterior ring" ).arg( i + 1 ).arg( partIndex );
219 QgsDebugMsgLevel( msg, 2 );
220 const QgsCurve *interiorRing = polygon->interiorRing( i );
221
222 if ( interiorRing->numPoints() == 0 )
223 emit errorFound( QgsGeometry::Error( msg ) );
224 else
225 emit errorFound( QgsGeometry::Error( msg, QgsPointXY( interiorRing->startPoint() ) ) );
226
227 mErrorCount++;
228 }
229 }
230
231 // check holes for intersections
232 for ( int i = 0; !mStop && i < polygon->numInteriorRings(); i++ )
233 {
234 for ( int j = i + 1; !mStop && j < polygon->numInteriorRings(); j++ )
235 {
236 checkRingIntersections( partIndex, i + 1, polygon->interiorRing( i ),
237 partIndex, j + 1, polygon->interiorRing( j ) );
238 }
239 }
240
241 // check if rings are self-intersecting
242 validatePolyline( 0, qgsgeometry_cast< const QgsLineString * >( polygon->exteriorRing() ), true );
243 for ( int i = 0; !mStop && i < polygon->numInteriorRings(); i++ )
244 {
245 validatePolyline( i + 1, qgsgeometry_cast< const QgsLineString * >( polygon->interiorRing( i ) ), true );
246 }
247}
248
250{
251 mErrorCount = 0;
252 if ( mGeometry.isNull() )
253 {
254 return;
255 }
256
257 switch ( mMethod )
258 {
260 {
261 // avoid calling geos for trivial point geometries
262 if ( QgsWkbTypes::geometryType( mGeometry.wkbType() ) == Qgis::GeometryType::Point )
263 {
264 return;
265 }
266
267 const QgsGeos geos( mGeometry.constGet(), 0, Qgis::GeosCreationFlags() );
268 QString error;
269 QgsGeometry errorLoc;
270 if ( !geos.isValid( &error, true, &errorLoc ) )
271 {
272 if ( errorLoc.isNull() )
273 {
274 emit errorFound( QgsGeometry::Error( error ) );
275 mErrorCount++;
276 }
277 else
278 {
279 const QgsPointXY point = errorLoc.asPoint();
280 emit errorFound( QgsGeometry::Error( error, point ) );
281 mErrorCount++;
282 }
283 }
284
285 break;
286 }
287
289 {
290 switch ( QgsWkbTypes::flatType( mGeometry.constGet()->wkbType() ) )
291 {
294 break;
295
297 validatePolyline( 0, qgsgeometry_cast< const QgsLineString * >( mGeometry.constGet() ) );
298 break;
299
301 {
302 const QgsGeometryCollection *collection = qgsgeometry_cast< const QgsGeometryCollection * >( mGeometry.constGet() );
303 for ( int i = 0; !mStop && i < collection->numGeometries(); i++ )
304 validatePolyline( i, qgsgeometry_cast< const QgsLineString * >( collection->geometryN( i ) ) );
305 break;
306 }
307
310 validatePolygon( 0, qgsgeometry_cast< const QgsCurvePolygon * >( mGeometry.constGet() ) );
311 break;
312
315 {
316 const QgsGeometryCollection *collection = qgsgeometry_cast< const QgsGeometryCollection * >( mGeometry.constGet() );
317 if ( !collection )
318 {
319 // should not be possible
320 break;
321 }
322
323 for ( int i = 0; !mStop && i < collection->numGeometries(); i++ )
324 validatePolygon( i, qgsgeometry_cast< const QgsCurvePolygon * >( collection->geometryN( i ) ) );
325
326 for ( int i = 0; !mStop && i < collection->numGeometries(); i++ )
327 {
329 if ( !poly->exteriorRing() || poly->exteriorRing()->isEmpty() )
330 {
331 emit errorFound( QgsGeometry::Error( QObject::tr( "Polygon %1 has no rings" ).arg( i ) ) );
332 mErrorCount++;
333 continue;
334 }
335
336 for ( int j = i + 1; !mStop && j < collection->numGeometries(); j++ )
337 {
339 if ( !poly2->exteriorRing() || poly2->exteriorRing()->isEmpty() )
340 continue;
341
342 if ( ringInRing( poly->exteriorRing(),
343 poly2->exteriorRing() ) )
344 {
345 emit errorFound( QgsGeometry::Error( QObject::tr( "Polygon %1 lies inside polygon %2" ).arg( i ).arg( j ) ) );
346 mErrorCount++;
347 }
348 else if ( ringInRing( poly2->exteriorRing(),
349 poly->exteriorRing() ) )
350 {
351 emit errorFound( QgsGeometry::Error( QObject::tr( "Polygon %1 lies inside polygon %2" ).arg( j ).arg( i ) ) );
352 mErrorCount++;
353 }
354 else
355 {
356 checkRingIntersections( i, 0, poly->exteriorRing(),
357 j, 0, poly2->exteriorRing() );
358 }
359 }
360 }
361 break;
362 }
363
365 {
366 emit errorFound( QgsGeometry::Error( QObject::tr( "Unknown geometry type %1" ).arg( qgsEnumValueToKey( mGeometry.wkbType() ) ) ) );
367 mErrorCount++;
368 break;
369 }
370
371 default:
372 break;
373 }
374
375 if ( mStop )
376 {
377 emit validationFinished( QObject::tr( "Geometry validation was aborted." ) );
378 }
379 else if ( mErrorCount > 0 )
380 {
381 emit validationFinished( QObject::tr( "Geometry has %n error(s).", nullptr, mErrorCount ) );
382 }
383 else
384 {
385 emit validationFinished( QObject::tr( "Geometry is valid." ) );
386 }
387 break;
388 }
389 }
390}
391
393{
394 if ( mErrors )
395 *mErrors << e;
396}
397
398void QgsGeometryValidator::validateGeometry( const QgsGeometry &geometry, QVector<QgsGeometry::Error> &errors, Qgis::GeometryValidationEngine method )
399{
400 QgsGeometryValidator *gv = new QgsGeometryValidator( geometry, &errors, method );
402 gv->run();
403 gv->wait();
404}
405
406//
407// distance of point q from line through p in direction v
408// return >0 => q lies left of the line
409// <0 => q lies right of the line
410//
411double QgsGeometryValidator::distLine2Point( double px, double py, QgsVector v, double qX, double qY )
412{
413 const double l = v.length();
414 if ( qgsDoubleNear( l, 0 ) )
415 {
416 throw QgsException( QObject::tr( "invalid line" ) );
417 }
418
419 return ( v.x() * ( qY - py ) - v.y() * ( qX - px ) ) / l;
420}
421
422bool QgsGeometryValidator::intersectLines( double px, double py, QgsVector v, double qx, double qy, QgsVector w, double &sX, double &sY )
423{
424 const double d = v.y() * w.x() - v.x() * w.y();
425
426 if ( qgsDoubleNear( d, 0 ) )
427 return false;
428
429 const double dx = qx - px;
430 const double dy = qy - py;
431 const double k = ( dy * w.x() - dx * w.y() ) / d;
432
433 sX = px + v.x() * k;
434 sY = py + v.y() * k;
435
436 return true;
437}
438
439bool QgsGeometryValidator::pointInRing( const QgsCurve *ring, double pX, double pY )
440{
441 if ( !ring->boundingBox().contains( pX, pY ) )
442 return false;
443
444 bool inside = false;
445 int j = ring->numPoints() - 1;
446
447 for ( int i = 0; !mStop && i < ring->numPoints(); i++ )
448 {
449 const double xAti = ring->xAt( i );
450 const double yAti = ring->yAt( i );
451 const double xAtj = ring->xAt( j );
452 const double yAtj = ring->yAt( j );
453
454 if ( qgsDoubleNear( xAti, pX ) && qgsDoubleNear( yAti, pY ) )
455 return true;
456
457 if ( ( yAti < pY && yAtj >= pY ) ||
458 ( yAtj < pY && yAti >= pY ) )
459 {
460 if ( xAti + ( pY - yAti ) / ( yAtj - yAti ) * ( xAtj - xAti ) <= pX )
461 inside = !inside;
462 }
463
464 j = i;
465 }
466
467 return inside;
468}
469
470bool QgsGeometryValidator::ringInRing( const QgsCurve *inside, const QgsCurve *outside )
471{
472 if ( !outside->boundingBox().contains( inside->boundingBox() ) )
473 return false;
474
475 for ( int i = 0; !mStop && i < inside->numPoints(); i++ )
476 {
477 if ( !pointInRing( outside, inside->xAt( i ), inside->yAt( i ) ) )
478 return false;
479 }
480
481 return true;
482}
GeometryValidationEngine
Available engines for validating geometries.
Definition qgis.h:2084
@ QgisInternal
Use internal QgsGeometryValidator method.
Definition qgis.h:2085
@ Geos
Use GEOS validation methods.
Definition qgis.h:2086
QFlags< GeosCreationFlag > GeosCreationFlags
Geos geometry creation behavior flags.
Definition qgis.h:2158
@ Point
Points.
Definition qgis.h:359
@ Point
Point.
Definition qgis.h:279
@ LineString
LineString.
Definition qgis.h:280
@ MultiPoint
MultiPoint.
Definition qgis.h:283
@ Polygon
Polygon.
Definition qgis.h:281
@ MultiPolygon
MultiPolygon.
Definition qgis.h:285
@ MultiLineString
MultiLineString.
Definition qgis.h:284
@ Unknown
Unknown.
Definition qgis.h:278
@ CurvePolygon
CurvePolygon.
Definition qgis.h:289
@ MultiSurface
MultiSurface.
Definition qgis.h:291
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.
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:176
virtual double xAt(int index) const =0
Returns the x-coordinate of the specified node in the line string.
virtual QgsPoint startPoint() const =0
Returns the starting point of the curve.
virtual double yAt(int index) const =0
Returns the y-coordinate of the specified node in the line string.
Defines a QGIS exception class.
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.
static bool segmentIntersection(double p1x, double p1y, double p2x, double p2y, double q1x, double q1y, double q2x, double q2y, double &intersectionPointX, double &intersectionPointY, bool &isIntersection, double tolerance=1e-8, bool acceptImproperIntersection=false)
Compute the intersection between two segments.
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.
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, and exception handling.
Definition qgsgeos.h:141
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.
Represents a 2D point.
Definition qgspointxy.h:60
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
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.
Represent a 2-dimensional vector.
Definition qgsvector.h:31
double y() const
Returns the vector's y-component.
Definition qgsvector.h:153
double x() const
Returns the vector's x-component.
Definition qgsvector.h:144
double length() const
Returns the length of the vector.
Definition qgsvector.h:125
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:77
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:6798
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6607
T qgsgeometry_cast(QgsAbstractGeometry *geom)
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:61
int vertex
Vertex number.
Definition qgsvertexid.h:94