QGIS API Documentation 4.3.0-Master (6402a64e93b)
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"
27#include "qgsvertexid.h"
28
29#include <QString>
30
31using namespace Qt::StringLiterals;
32
33#ifdef WITH_SFCGAL
34#include "qgssfcgalgeometry.h"
35#endif
36
37#include "moc_qgsgeometryvalidator.cpp"
38
39QgsGeometryValidator::QgsGeometryValidator( const QgsGeometry &geometry, QVector<QgsGeometry::Error> *errors, Qgis::GeometryValidationEngine method )
40 : mGeometry( geometry )
41 , mErrors( errors )
42 , mMethod( method )
43{}
44
50
52{
53 mStop = true;
54}
55
56void QgsGeometryValidator::checkRingIntersections( int partIndex0, int ringIndex0, const QgsCurve *ring0, int partIndex1, int ringIndex1, const QgsCurve *ring1 )
57{
59 std::unique_ptr< QgsLineString > segmentisedRing0;
60 if ( !ringLine0 )
61 {
62 segmentisedRing0.reset( qgsgeometry_cast< QgsLineString * >( ring0->segmentize() ) );
63 ringLine0 = segmentisedRing0.get();
64 }
65
66 const QgsLineString *ringLine1 = qgsgeometry_cast< const QgsLineString * >( ring1 );
67 std::unique_ptr< QgsLineString > segmentisedRing1;
68 if ( !ringLine1 )
69 {
70 segmentisedRing1.reset( qgsgeometry_cast< QgsLineString * >( ring1->segmentize() ) );
71 ringLine1 = segmentisedRing1.get();
72 }
73
74 Q_ASSERT( ringLine0 );
75 Q_ASSERT( ringLine1 );
76
77 for ( int i = 0; !mStop && i < ringLine0->numPoints() - 1; i++ )
78 {
79 const double ring0XAti = ringLine0->xAt( i );
80 const double ring0YAti = ringLine0->yAt( i );
81 const QgsVector v( ringLine0->xAt( i + 1 ) - ring0XAti, ringLine0->yAt( i + 1 ) - ring0YAti );
82
83 for ( int j = 0; !mStop && j < ringLine1->numPoints() - 1; j++ )
84 {
85 const double ring1XAtj = ringLine1->xAt( j );
86 const double ring1YAtj = ringLine1->yAt( j );
87 const QgsVector w( ringLine1->xAt( j + 1 ) - ring1XAtj, ringLine1->yAt( j + 1 ) - ring1YAtj );
88
89 double sX;
90 double sY;
91 if ( intersectLines( ring0XAti, ring0YAti, v, ring1XAtj, ring1YAtj, w, sX, sY ) )
92 {
93 double d = -distLine2Point( ring0XAti, ring0YAti, v.perpVector(), sX, sY );
94
95 if ( d >= 0 && d <= v.length() )
96 {
97 d = -distLine2Point( ring1XAtj, ring1YAtj, w.perpVector(), sX, sY );
98 if ( d > 0
99 && d < w.length()
100 && ringLine0->pointN( i + 1 ) != ringLine1->pointN( j + 1 )
101 && ringLine0->pointN( i + 1 ) != ringLine1->pointN( j )
102 && ringLine0->pointN( i + 0 ) != ringLine1->pointN( j + 1 )
103 && ringLine0->pointN( i + 0 ) != ringLine1->pointN( j ) )
104 {
105 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" )
106 .arg( i )
107 .arg( ringIndex0 )
108 .arg( partIndex0 )
109 .arg( j )
110 .arg( ringIndex1 )
111 .arg( partIndex1 )
112 .arg( sX )
113 .arg( sY );
114 emit errorFound( QgsGeometry::Error( msg, QgsPointXY( sX, sY ) ) );
115 mErrorCount++;
116 }
117 }
118 }
119 }
120 }
121}
122
123void QgsGeometryValidator::validatePolyline( int i, const QgsLineString *line, bool ring )
124{
125 if ( !line )
126 return;
127
128 if ( ring )
129 {
130 if ( line->numPoints() < 4 )
131 {
132 const QString msg = QObject::tr( "ring %1 with less than four points" ).arg( i );
133 QgsDebugMsgLevel( msg, 2 );
134 emit errorFound( QgsGeometry::Error( msg ) );
135 mErrorCount++;
136 return;
137 }
138
139 if ( !line->isClosed() )
140 {
141 const QgsPoint startPoint = line->startPoint();
142 const QgsPoint endPoint = line->endPoint();
143 QString msg;
144 if ( line->is3D() && line->isClosed2D() )
145 {
146 msg = QObject::tr( "ring %1 not closed, Z mismatch: %2 vs %3" ).arg( i ).arg( startPoint.z() ).arg( endPoint.z() );
147 }
148 else
149 {
150 msg = QObject::tr( "ring %1 not closed" ).arg( i );
151 QgsDebugMsgLevel( msg, 2 );
152 }
153 emit errorFound( QgsGeometry::Error( msg, QgsPointXY( startPoint.x(), startPoint.y() ) ) );
154 mErrorCount++;
155 return;
156 }
157 }
158 else if ( line->numPoints() < 2 )
159 {
160 const QString msg = QObject::tr( "line %1 with less than two points" ).arg( i );
161 QgsDebugMsgLevel( msg, 2 );
162 emit errorFound( QgsGeometry::Error( msg ) );
163 mErrorCount++;
164 return;
165 }
166
167 std::unique_ptr< QgsLineString > noDupes;
168
169 // test for duplicate nodes, and if we find any flag errors and then remove them so that the subsequent
170 // tests work OK.
171 const QVector< QgsVertexId > duplicateNodes = line->collectDuplicateNodes( 1E-8 );
172 if ( !duplicateNodes.empty() )
173 {
174 noDupes.reset( line->clone() );
175 for ( int j = duplicateNodes.size() - 1; j >= 0; j-- )
176 {
177 const QgsVertexId duplicateVertex = duplicateNodes.at( j );
178 const QgsPointXY duplicationLocation = noDupes->vertexAt( duplicateVertex );
179 noDupes->deleteVertex( duplicateVertex );
180 int n = 1;
181
182 // count how many other points exist at this location too
183 for ( int k = j - 1; k >= 0; k-- )
184 {
185 const QgsVertexId prevDupe = duplicateNodes.at( k );
186 const QgsPoint prevPoint = noDupes->vertexAt( prevDupe );
187 if ( qgsDoubleNear( duplicationLocation.x(), prevPoint.x(), 1E-8 ) && qgsDoubleNear( duplicationLocation.y(), prevPoint.y(), 1E-8 ) )
188 {
189 noDupes->deleteVertex( prevDupe );
190 n++;
191 }
192 else
193 {
194 break;
195 }
196 }
197
198 j -= n - 1;
199
200 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 );
201 QgsDebugMsgLevel( msg, 2 );
202 emit errorFound( QgsGeometry::Error( msg, duplicationLocation ) );
203 mErrorCount++;
204 }
205 line = noDupes.get();
206 }
207
208 // segment Intersection
209 for ( int j = 0; !mStop && j < line->numPoints() - 3; j++ )
210 {
211 const int n = ( j == 0 && ring ) ? line->numPoints() - 2 : line->numPoints() - 1;
212 for ( int k = j + 2; !mStop && k < n; k++ )
213 {
214 double intersectionPointX = std::numeric_limits<double>::quiet_NaN();
215 double intersectionPointY = std::numeric_limits<double>::quiet_NaN();
216 bool isIntersection = false;
217 if ( QgsGeometryUtilsBase::
218 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 ) )
219 {
220 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 );
221 QgsDebugMsgLevel( msg, 2 );
222 emit errorFound( QgsGeometry::Error( msg, QgsPointXY( intersectionPointX, intersectionPointY ) ) );
223 mErrorCount++;
224 }
225 }
226 }
227}
228
229void QgsGeometryValidator::validatePolygon( int partIndex, const QgsCurvePolygon *polygon )
230{
231 // check if holes are inside polygon
232 for ( int i = 0; !mStop && i < polygon->numInteriorRings(); ++i )
233 {
234 if ( !ringInRing( polygon->interiorRing( i ), polygon->exteriorRing() ) )
235 {
236 const QString msg = QObject::tr( "ring %1 of polygon %2 not in exterior ring" ).arg( i + 1 ).arg( partIndex );
237 QgsDebugMsgLevel( msg, 2 );
238 const QgsCurve *interiorRing = polygon->interiorRing( i );
239
240 if ( interiorRing->numPoints() == 0 )
241 emit errorFound( QgsGeometry::Error( msg ) );
242 else
243 emit errorFound( QgsGeometry::Error( msg, QgsPointXY( interiorRing->startPoint() ) ) );
244
245 mErrorCount++;
246 }
247 }
248
249 // check holes for intersections
250 for ( int i = 0; !mStop && i < polygon->numInteriorRings(); i++ )
251 {
252 for ( int j = i + 1; !mStop && j < polygon->numInteriorRings(); j++ )
253 {
254 checkRingIntersections( partIndex, i + 1, polygon->interiorRing( i ), partIndex, j + 1, polygon->interiorRing( j ) );
255 }
256 }
257
258 // check if rings are self-intersecting
259 validatePolyline( 0, qgsgeometry_cast< const QgsLineString * >( polygon->exteriorRing() ), true );
260 for ( int i = 0; !mStop && i < polygon->numInteriorRings(); i++ )
261 {
262 validatePolyline( i + 1, qgsgeometry_cast< const QgsLineString * >( polygon->interiorRing( i ) ), true );
263 }
264}
265
267{
268 mErrorCount = 0;
269 if ( mGeometry.isNull() )
270 {
271 return;
272 }
273
274 switch ( mMethod )
275 {
277 {
278 // avoid calling geos for trivial point geometries
279 if ( QgsWkbTypes::geometryType( mGeometry.wkbType() ) == Qgis::GeometryType::Point )
280 {
281 return;
282 }
283
284 const QgsGeos geos( mGeometry.constGet(), 0, Qgis::GeosCreationFlags() );
285 QString error;
286 QgsGeometry errorLoc;
287 if ( !geos.isValid( &error, true, &errorLoc ) )
288 {
289 if ( errorLoc.isNull() )
290 {
291 emit errorFound( QgsGeometry::Error( error ) );
292 mErrorCount++;
293 }
294 else
295 {
296 const QgsPointXY point = errorLoc.asPoint();
297 emit errorFound( QgsGeometry::Error( error, point ) );
298 mErrorCount++;
299 }
300 }
301
302 break;
303 }
304
306 {
307 switch ( QgsWkbTypes::flatType( mGeometry.constGet()->wkbType() ) )
308 {
311 break;
312
314 validatePolyline( 0, qgsgeometry_cast< const QgsLineString * >( mGeometry.constGet() ) );
315 break;
316
318 {
319 const QgsGeometryCollection *collection = qgsgeometry_cast< const QgsGeometryCollection * >( mGeometry.constGet() );
320 for ( int i = 0; !mStop && i < collection->numGeometries(); i++ )
321 validatePolyline( i, qgsgeometry_cast< const QgsLineString * >( collection->geometryN( i ) ) );
322 break;
323 }
324
327 validatePolygon( 0, qgsgeometry_cast< const QgsCurvePolygon * >( mGeometry.constGet() ) );
328 break;
329
332 {
333 const QgsGeometryCollection *collection = qgsgeometry_cast< const QgsGeometryCollection * >( mGeometry.constGet() );
334 if ( !collection )
335 {
336 // should not be possible
337 break;
338 }
339
340 for ( int i = 0; !mStop && i < collection->numGeometries(); i++ )
341 validatePolygon( i, qgsgeometry_cast< const QgsCurvePolygon * >( collection->geometryN( i ) ) );
342
343 for ( int i = 0; !mStop && i < collection->numGeometries(); i++ )
344 {
346 if ( !poly->exteriorRing() || poly->exteriorRing()->isEmpty() )
347 {
348 emit errorFound( QgsGeometry::Error( QObject::tr( "Polygon %1 has no rings" ).arg( i ) ) );
349 mErrorCount++;
350 continue;
351 }
352
353 for ( int j = i + 1; !mStop && j < collection->numGeometries(); j++ )
354 {
356 if ( !poly2->exteriorRing() || poly2->exteriorRing()->isEmpty() )
357 continue;
358
359 if ( ringInRing( poly->exteriorRing(), poly2->exteriorRing() ) )
360 {
361 emit errorFound( QgsGeometry::Error( QObject::tr( "Polygon %1 lies inside polygon %2" ).arg( i ).arg( j ) ) );
362 mErrorCount++;
363 }
364 else if ( ringInRing( poly2->exteriorRing(), poly->exteriorRing() ) )
365 {
366 emit errorFound( QgsGeometry::Error( QObject::tr( "Polygon %1 lies inside polygon %2" ).arg( j ).arg( i ) ) );
367 mErrorCount++;
368 }
369 else
370 {
371 checkRingIntersections( i, 0, poly->exteriorRing(), j, 0, poly2->exteriorRing() );
372 }
373 }
374 }
375 break;
376 }
377
379 {
380 emit errorFound( QgsGeometry::Error( QObject::tr( "Unknown geometry type %1" ).arg( qgsEnumValueToKey( mGeometry.wkbType() ) ) ) );
381 mErrorCount++;
382 break;
383 }
384
385 default:
386 break;
387 }
388
389 if ( mStop )
390 {
391 emit validationFinished( QObject::tr( "Geometry validation was aborted." ) );
392 }
393 else if ( mErrorCount > 0 )
394 {
395 emit validationFinished( QObject::tr( "Geometry has %n error(s).", nullptr, mErrorCount ) );
396 }
397 else
398 {
399 emit validationFinished( QObject::tr( "Geometry is valid." ) );
400 }
401 break;
402 }
403
405 {
406 // avoid calling SFCGAL for trivial point geometries
407 if ( QgsWkbTypes::geometryType( mGeometry.wkbType() ) == Qgis::GeometryType::Point )
408 {
409 return;
410 }
411
412#ifdef WITH_SFCGAL
413 QString errorMsg;
414 QgsGeometry errorLoc;
415 const QgsSfcgalGeometry sfcgalGeom( mGeometry.constGet() );
416 if ( !QgsSfcgalEngine::isValid( sfcgalGeom.sfcgalGeometry().get(), nullptr, &errorMsg, &errorLoc ) )
417 {
418 if ( errorLoc.isNull() )
419 {
420 emit errorFound( QgsGeometry::Error( errorMsg ) );
421 mErrorCount++;
422 }
423 else
424 {
425 const QgsPointXY point = errorLoc.asPoint();
426 emit errorFound( QgsGeometry::Error( errorMsg, point ) );
427 mErrorCount++;
428 }
429 }
430#else
431 QgsDebugError( u"Cannot use SFCGAL validation method in this QGIS build."_s );
432#endif
433
434 break;
435 }
436 }
437}
438
440{
441 if ( mErrors )
442 *mErrors << e;
443}
444
445void QgsGeometryValidator::validateGeometry( const QgsGeometry &geometry, QVector<QgsGeometry::Error> &errors, Qgis::GeometryValidationEngine method )
446{
447 QgsGeometryValidator *gv = new QgsGeometryValidator( geometry, &errors, method );
449 gv->run();
450 gv->wait();
451}
452
453//
454// distance of point q from line through p in direction v
455// return >0 => q lies left of the line
456// <0 => q lies right of the line
457//
458double QgsGeometryValidator::distLine2Point( double px, double py, QgsVector v, double qX, double qY )
459{
460 const double l = v.length();
461 if ( qgsDoubleNear( l, 0 ) )
462 {
463 throw QgsException( QObject::tr( "invalid line" ) );
464 }
465
466 return ( v.x() * ( qY - py ) - v.y() * ( qX - px ) ) / l;
467}
468
469bool QgsGeometryValidator::intersectLines( double px, double py, QgsVector v, double qx, double qy, QgsVector w, double &sX, double &sY )
470{
471 const double d = v.y() * w.x() - v.x() * w.y();
472
473 if ( qgsDoubleNear( d, 0 ) )
474 return false;
475
476 const double dx = qx - px;
477 const double dy = qy - py;
478 const double k = ( dy * w.x() - dx * w.y() ) / d;
479
480 sX = px + v.x() * k;
481 sY = py + v.y() * k;
482
483 return true;
484}
485
486bool QgsGeometryValidator::pointInRing( const QgsCurve *ring, double pX, double pY )
487{
488 if ( !ring->boundingBox().contains( pX, pY ) )
489 return false;
490
491 bool inside = false;
492 int j = ring->numPoints() - 1;
493
494 for ( int i = 0; !mStop && i < ring->numPoints(); i++ )
495 {
496 const double xAti = ring->xAt( i );
497 const double yAti = ring->yAt( i );
498 const double xAtj = ring->xAt( j );
499 const double yAtj = ring->yAt( j );
500
501 if ( qgsDoubleNear( xAti, pX ) && qgsDoubleNear( yAti, pY ) )
502 return true;
503
504 if ( ( yAti < pY && yAtj >= pY ) || ( yAtj < pY && yAti >= pY ) )
505 {
506 if ( xAti + ( pY - yAti ) / ( yAtj - yAti ) * ( xAtj - xAti ) <= pX )
507 inside = !inside;
508 }
509
510 j = i;
511 }
512
513 return inside;
514}
515
516bool QgsGeometryValidator::ringInRing( const QgsCurve *inside, const QgsCurve *outside )
517{
518 if ( !outside->boundingBox().contains( inside->boundingBox() ) )
519 return false;
520
521 for ( int i = 0; !mStop && i < inside->numPoints(); i++ )
522 {
523 if ( !pointInRing( outside, inside->xAt( i ), inside->yAt( i ) ) )
524 return false;
525 }
526
527 return true;
528}
529
GeometryValidationEngine
Available engines for validating geometries.
Definition qgis.h:2235
@ QgisInternal
Use internal QgsGeometryValidator method.
Definition qgis.h:2236
@ Sfcgal
Use SFCGAL validation methods. Only available for QGIS builds with SFCGAL support enabled.
Definition qgis.h:2238
@ Geos
Use GEOS validation methods.
Definition qgis.h:2237
QFlags< GeosCreationFlag > GeosCreationFlags
Geos geometry creation behavior flags.
Definition qgis.h:2322
@ 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 &)
static Qgis::GeometryValidationEngine defaultValidationEngine()
Returns the geometry validation engine configured in the application settings.
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:175
Line string geometry type, with support for z-dimension and m-values.
bool isClosed() const override
Returns true if the curve is closed.
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.
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.
static const QgsSettingsEntryInteger * settingsDigitizingValidateGeometries
Settings entry digitizing validate geometries.
double yAt(int index) const override
Returns the y-coordinate of the specified node in the line string.
int numPoints() const override
Returns the number of points in the curve.
double xAt(int index) const override
Returns the x-coordinate of the specified node in the line string.
QgsPoint startPoint() const override
Returns the starting point of the curve.
QgsPoint endPoint() const override
Returns the end point of the curve.
QgsPoint pointN(int i) const
Returns the specified point from inside the simple curve.
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:112
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:7724
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:7488
T qgsgeometry_cast(QgsAbstractGeometry *geom)
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:80
#define QgsDebugError(str)
Definition qgslogger.h:71
int vertex
Vertex number.