QGIS API Documentation 4.1.0-Master (60fea48833c)
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
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{
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
89 && d < w.length()
90 && ringLine0->pointN( i + 1 ) != ringLine1->pointN( j + 1 )
91 && ringLine0->pointN( i + 1 ) != ringLine1->pointN( j )
92 && ringLine0->pointN( i + 0 ) != ringLine1->pointN( j + 1 )
93 && ringLine0->pointN( i + 0 ) != ringLine1->pointN( j ) )
94 {
95 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" )
96 .arg( i )
97 .arg( ringIndex0 )
98 .arg( partIndex0 )
99 .arg( j )
100 .arg( ringIndex1 )
101 .arg( partIndex1 )
102 .arg( sX )
103 .arg( sY );
104 emit errorFound( QgsGeometry::Error( msg, QgsPointXY( sX, sY ) ) );
105 mErrorCount++;
106 }
107 }
108 }
109 }
110 }
111}
112
113void QgsGeometryValidator::validatePolyline( int i, const QgsLineString *line, bool ring )
114{
115 if ( !line )
116 return;
117
118 if ( ring )
119 {
120 if ( line->numPoints() < 4 )
121 {
122 const QString msg = QObject::tr( "ring %1 with less than four points" ).arg( i );
123 QgsDebugMsgLevel( msg, 2 );
124 emit errorFound( QgsGeometry::Error( msg ) );
125 mErrorCount++;
126 return;
127 }
128
129 if ( !line->isClosed() )
130 {
131 const QgsPoint startPoint = line->startPoint();
132 const QgsPoint endPoint = line->endPoint();
133 QString msg;
134 if ( line->is3D() && line->isClosed2D() )
135 {
136 msg = QObject::tr( "ring %1 not closed, Z mismatch: %2 vs %3" ).arg( i ).arg( startPoint.z() ).arg( endPoint.z() );
137 }
138 else
139 {
140 msg = QObject::tr( "ring %1 not closed" ).arg( i );
141 QgsDebugMsgLevel( msg, 2 );
142 }
143 emit errorFound( QgsGeometry::Error( msg, QgsPointXY( startPoint.x(), startPoint.y() ) ) );
144 mErrorCount++;
145 return;
146 }
147 }
148 else if ( line->numPoints() < 2 )
149 {
150 const QString msg = QObject::tr( "line %1 with less than two points" ).arg( i );
151 QgsDebugMsgLevel( msg, 2 );
152 emit errorFound( QgsGeometry::Error( msg ) );
153 mErrorCount++;
154 return;
155 }
156
157 std::unique_ptr< QgsLineString > noDupes;
158
159 // test for duplicate nodes, and if we find any flag errors and then remove them so that the subsequent
160 // tests work OK.
161 const QVector< QgsVertexId > duplicateNodes = line->collectDuplicateNodes( 1E-8 );
162 if ( !duplicateNodes.empty() )
163 {
164 noDupes.reset( line->clone() );
165 for ( int j = duplicateNodes.size() - 1; j >= 0; j-- )
166 {
167 const QgsVertexId duplicateVertex = duplicateNodes.at( j );
168 const QgsPointXY duplicationLocation = noDupes->vertexAt( duplicateVertex );
169 noDupes->deleteVertex( duplicateVertex );
170 int n = 1;
171
172 // count how many other points exist at this location too
173 for ( int k = j - 1; k >= 0; k-- )
174 {
175 const QgsVertexId prevDupe = duplicateNodes.at( k );
176 const QgsPoint prevPoint = noDupes->vertexAt( prevDupe );
177 if ( qgsDoubleNear( duplicationLocation.x(), prevPoint.x(), 1E-8 ) && qgsDoubleNear( duplicationLocation.y(), prevPoint.y(), 1E-8 ) )
178 {
179 noDupes->deleteVertex( prevDupe );
180 n++;
181 }
182 else
183 {
184 break;
185 }
186 }
187
188 j -= n - 1;
189
190 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 );
191 QgsDebugMsgLevel( msg, 2 );
192 emit errorFound( QgsGeometry::Error( msg, duplicationLocation ) );
193 mErrorCount++;
194 }
195 line = noDupes.get();
196 }
197
198 // segment Intersection
199 for ( int j = 0; !mStop && j < line->numPoints() - 3; j++ )
200 {
201 const int n = ( j == 0 && ring ) ? line->numPoints() - 2 : line->numPoints() - 1;
202 for ( int k = j + 2; !mStop && k < n; k++ )
203 {
204 double intersectionPointX = std::numeric_limits<double>::quiet_NaN();
205 double intersectionPointY = std::numeric_limits<double>::quiet_NaN();
206 bool isIntersection = false;
207 if ( QgsGeometryUtilsBase::
208 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 ) )
209 {
210 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 );
211 QgsDebugMsgLevel( msg, 2 );
212 emit errorFound( QgsGeometry::Error( msg, QgsPointXY( intersectionPointX, intersectionPointY ) ) );
213 mErrorCount++;
214 }
215 }
216 }
217}
218
219void QgsGeometryValidator::validatePolygon( int partIndex, const QgsCurvePolygon *polygon )
220{
221 // check if holes are inside polygon
222 for ( int i = 0; !mStop && i < polygon->numInteriorRings(); ++i )
223 {
224 if ( !ringInRing( polygon->interiorRing( i ), polygon->exteriorRing() ) )
225 {
226 const QString msg = QObject::tr( "ring %1 of polygon %2 not in exterior ring" ).arg( i + 1 ).arg( partIndex );
227 QgsDebugMsgLevel( msg, 2 );
228 const QgsCurve *interiorRing = polygon->interiorRing( i );
229
230 if ( interiorRing->numPoints() == 0 )
231 emit errorFound( QgsGeometry::Error( msg ) );
232 else
233 emit errorFound( QgsGeometry::Error( msg, QgsPointXY( interiorRing->startPoint() ) ) );
234
235 mErrorCount++;
236 }
237 }
238
239 // check holes for intersections
240 for ( int i = 0; !mStop && i < polygon->numInteriorRings(); i++ )
241 {
242 for ( int j = i + 1; !mStop && j < polygon->numInteriorRings(); j++ )
243 {
244 checkRingIntersections( partIndex, i + 1, polygon->interiorRing( i ), partIndex, j + 1, polygon->interiorRing( j ) );
245 }
246 }
247
248 // check if rings are self-intersecting
249 validatePolyline( 0, qgsgeometry_cast< const QgsLineString * >( polygon->exteriorRing() ), true );
250 for ( int i = 0; !mStop && i < polygon->numInteriorRings(); i++ )
251 {
252 validatePolyline( i + 1, qgsgeometry_cast< const QgsLineString * >( polygon->interiorRing( i ) ), true );
253 }
254}
255
257{
258 mErrorCount = 0;
259 if ( mGeometry.isNull() )
260 {
261 return;
262 }
263
264 switch ( mMethod )
265 {
267 {
268 // avoid calling geos for trivial point geometries
269 if ( QgsWkbTypes::geometryType( mGeometry.wkbType() ) == Qgis::GeometryType::Point )
270 {
271 return;
272 }
273
274 const QgsGeos geos( mGeometry.constGet(), 0, Qgis::GeosCreationFlags() );
275 QString error;
276 QgsGeometry errorLoc;
277 if ( !geos.isValid( &error, true, &errorLoc ) )
278 {
279 if ( errorLoc.isNull() )
280 {
281 emit errorFound( QgsGeometry::Error( error ) );
282 mErrorCount++;
283 }
284 else
285 {
286 const QgsPointXY point = errorLoc.asPoint();
287 emit errorFound( QgsGeometry::Error( error, point ) );
288 mErrorCount++;
289 }
290 }
291
292 break;
293 }
294
296 {
297 switch ( QgsWkbTypes::flatType( mGeometry.constGet()->wkbType() ) )
298 {
301 break;
302
304 validatePolyline( 0, qgsgeometry_cast< const QgsLineString * >( mGeometry.constGet() ) );
305 break;
306
308 {
309 const QgsGeometryCollection *collection = qgsgeometry_cast< const QgsGeometryCollection * >( mGeometry.constGet() );
310 for ( int i = 0; !mStop && i < collection->numGeometries(); i++ )
311 validatePolyline( i, qgsgeometry_cast< const QgsLineString * >( collection->geometryN( i ) ) );
312 break;
313 }
314
317 validatePolygon( 0, qgsgeometry_cast< const QgsCurvePolygon * >( mGeometry.constGet() ) );
318 break;
319
322 {
323 const QgsGeometryCollection *collection = qgsgeometry_cast< const QgsGeometryCollection * >( mGeometry.constGet() );
324 if ( !collection )
325 {
326 // should not be possible
327 break;
328 }
329
330 for ( int i = 0; !mStop && i < collection->numGeometries(); i++ )
331 validatePolygon( i, qgsgeometry_cast< const QgsCurvePolygon * >( collection->geometryN( i ) ) );
332
333 for ( int i = 0; !mStop && i < collection->numGeometries(); i++ )
334 {
336 if ( !poly->exteriorRing() || poly->exteriorRing()->isEmpty() )
337 {
338 emit errorFound( QgsGeometry::Error( QObject::tr( "Polygon %1 has no rings" ).arg( i ) ) );
339 mErrorCount++;
340 continue;
341 }
342
343 for ( int j = i + 1; !mStop && j < collection->numGeometries(); j++ )
344 {
346 if ( !poly2->exteriorRing() || poly2->exteriorRing()->isEmpty() )
347 continue;
348
349 if ( ringInRing( poly->exteriorRing(), poly2->exteriorRing() ) )
350 {
351 emit errorFound( QgsGeometry::Error( QObject::tr( "Polygon %1 lies inside polygon %2" ).arg( i ).arg( j ) ) );
352 mErrorCount++;
353 }
354 else if ( ringInRing( poly2->exteriorRing(), poly->exteriorRing() ) )
355 {
356 emit errorFound( QgsGeometry::Error( QObject::tr( "Polygon %1 lies inside polygon %2" ).arg( j ).arg( i ) ) );
357 mErrorCount++;
358 }
359 else
360 {
361 checkRingIntersections( i, 0, poly->exteriorRing(), j, 0, poly2->exteriorRing() );
362 }
363 }
364 }
365 break;
366 }
367
369 {
370 emit errorFound( QgsGeometry::Error( QObject::tr( "Unknown geometry type %1" ).arg( qgsEnumValueToKey( mGeometry.wkbType() ) ) ) );
371 mErrorCount++;
372 break;
373 }
374
375 default:
376 break;
377 }
378
379 if ( mStop )
380 {
381 emit validationFinished( QObject::tr( "Geometry validation was aborted." ) );
382 }
383 else if ( mErrorCount > 0 )
384 {
385 emit validationFinished( QObject::tr( "Geometry has %n error(s).", nullptr, mErrorCount ) );
386 }
387 else
388 {
389 emit validationFinished( QObject::tr( "Geometry is valid." ) );
390 }
391 break;
392 }
393 }
394}
395
397{
398 if ( mErrors )
399 *mErrors << e;
400}
401
402void QgsGeometryValidator::validateGeometry( const QgsGeometry &geometry, QVector<QgsGeometry::Error> &errors, Qgis::GeometryValidationEngine method )
403{
404 QgsGeometryValidator *gv = new QgsGeometryValidator( geometry, &errors, method );
406 gv->run();
407 gv->wait();
408}
409
410//
411// distance of point q from line through p in direction v
412// return >0 => q lies left of the line
413// <0 => q lies right of the line
414//
415double QgsGeometryValidator::distLine2Point( double px, double py, QgsVector v, double qX, double qY )
416{
417 const double l = v.length();
418 if ( qgsDoubleNear( l, 0 ) )
419 {
420 throw QgsException( QObject::tr( "invalid line" ) );
421 }
422
423 return ( v.x() * ( qY - py ) - v.y() * ( qX - px ) ) / l;
424}
425
426bool QgsGeometryValidator::intersectLines( double px, double py, QgsVector v, double qx, double qy, QgsVector w, double &sX, double &sY )
427{
428 const double d = v.y() * w.x() - v.x() * w.y();
429
430 if ( qgsDoubleNear( d, 0 ) )
431 return false;
432
433 const double dx = qx - px;
434 const double dy = qy - py;
435 const double k = ( dy * w.x() - dx * w.y() ) / d;
436
437 sX = px + v.x() * k;
438 sY = py + v.y() * k;
439
440 return true;
441}
442
443bool QgsGeometryValidator::pointInRing( const QgsCurve *ring, double pX, double pY )
444{
445 if ( !ring->boundingBox().contains( pX, pY ) )
446 return false;
447
448 bool inside = false;
449 int j = ring->numPoints() - 1;
450
451 for ( int i = 0; !mStop && i < ring->numPoints(); i++ )
452 {
453 const double xAti = ring->xAt( i );
454 const double yAti = ring->yAt( i );
455 const double xAtj = ring->xAt( j );
456 const double yAtj = ring->yAt( j );
457
458 if ( qgsDoubleNear( xAti, pX ) && qgsDoubleNear( yAti, pY ) )
459 return true;
460
461 if ( ( yAti < pY && yAtj >= pY ) || ( yAtj < pY && yAti >= pY ) )
462 {
463 if ( xAti + ( pY - yAti ) / ( yAtj - yAti ) * ( xAtj - xAti ) <= pX )
464 inside = !inside;
465 }
466
467 j = i;
468 }
469
470 return inside;
471}
472
473bool QgsGeometryValidator::ringInRing( const QgsCurve *inside, const QgsCurve *outside )
474{
475 if ( !outside->boundingBox().contains( inside->boundingBox() ) )
476 return false;
477
478 for ( int i = 0; !mStop && i < inside->numPoints(); i++ )
479 {
480 if ( !pointInRing( outside, inside->xAt( i ), inside->yAt( i ) ) )
481 return false;
482 }
483
484 return true;
485}
GeometryValidationEngine
Available engines for validating geometries.
Definition qgis.h:2164
@ QgisInternal
Use internal QgsGeometryValidator method.
Definition qgis.h:2165
@ Geos
Use GEOS validation methods.
Definition qgis.h:2166
QFlags< GeosCreationFlag > GeosCreationFlags
Geos geometry creation behavior flags.
Definition qgis.h:2238
@ Point
Points.
Definition qgis.h:380
@ Point
Point.
Definition qgis.h:296
@ LineString
LineString.
Definition qgis.h:297
@ MultiPoint
MultiPoint.
Definition qgis.h:300
@ Polygon
Polygon.
Definition qgis.h:298
@ MultiPolygon
MultiPolygon.
Definition qgis.h:302
@ MultiLineString
MultiLineString.
Definition qgis.h:301
@ Unknown
Unknown.
Definition qgis.h:295
@ CurvePolygon
CurvePolygon.
Definition qgis.h:306
@ MultiSurface
MultiSurface.
Definition qgis.h:308
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:175
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.
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:139
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:62
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
double z
Definition qgspoint.h:58
double x
Definition qgspoint.h:56
double y
Definition qgspoint.h:57
bool contains(const QgsRectangle &rect) const
Returns true when rectangle contains other rectangle.
Represent a 2-dimensional vector.
Definition qgsvector.h:34
double y() const
Returns the vector's y-component.
Definition qgsvector.h:155
double x() const
Returns the vector's x-component.
Definition qgsvector.h:146
double length() const
Returns the length of the vector.
Definition qgsvector.h:127
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:76
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:7157
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6975
T qgsgeometry_cast(QgsAbstractGeometry *geom)
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63
int vertex
Vertex number.
Definition qgsvertexid.h:99