QGIS API Documentation 3.32.0-Lima (311a8cb8a6)
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
36{
37 stop();
38 wait();
39}
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 {
289 case Qgis::GeometryValidationEngine::Geos:
290 {
291 // avoid calling geos for trivial point geometries
292 if ( QgsWkbTypes::geometryType( mGeometry.wkbType() ) == Qgis::GeometryType::Point )
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
318 case Qgis::GeometryValidationEngine::QgisInternal:
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:1465
@ LineString
LineString.
@ MultiPoint
MultiPoint.
@ Polygon
Polygon.
@ MultiPolygon
MultiPolygon.
@ MultiLineString
MultiLineString.
@ Unknown
Unknown.
@ CurvePolygon
CurvePolygon.
@ MultiSurface
MultiSurface.
bool is3D() const SIP_HOLDGIL
Returns true if the geometry is 3D and contains a z-value.
virtual bool isEmpty() const
Returns true if the geometry is empty.
Qgis::WkbType wkbType() const SIP_HOLDGIL
Returns the WKB type of the geometry.
Curve polygon geometry type.
const QgsCurve * interiorRing(int i) const SIP_HOLDGIL
Retrieves an interior ring from the curve polygon.
const QgsCurve * exteriorRing() const SIP_HOLDGIL
Returns the curve polygon's exterior ring.
int numInteriorRings() const SIP_HOLDGIL
Returns the number of interior rings contained with 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.
Definition: qgsexception.h:35
QString what() const
Definition: qgsexception.h:49
Geometry collection.
int numGeometries() const SIP_HOLDGIL
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.
Definition: qgsgeometry.h:2595
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:164
const QgsAbstractGeometry * constGet() const SIP_HOLDGIL
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
Q_GADGET bool isNull
Definition: qgsgeometry.h:166
Qgis::WkbType wkbType() const SIP_HOLDGIL
Returns type of the geometry as a WKB type (point / linestring / polygon etc.)
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.
Definition: qgslinestring.h:45
QgsPoint startPoint() const override SIP_HOLDGIL
Returns the starting point of the curve.
bool isClosed() const override SIP_HOLDGIL
Returns true if the curve is closed.
QgsPoint endPoint() const override SIP_HOLDGIL
Returns the end point of the curve.
int numPoints() const override SIP_HOLDGIL
Returns the number of points in the curve.
QgsPoint pointN(int i) const
Returns the specified point from inside the line string.
bool isClosed2D() const override SIP_HOLDGIL
Returns true if the curve is closed.
double yAt(int index) const override
Returns the y-coordinate of the specified node in the line string.
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.
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
Q_GADGET double x
Definition: qgspointxy.h:62
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:49
Q_GADGET double x
Definition: qgspoint.h:52
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 y
Definition: qgspoint.h:53
bool contains(const QgsRectangle &rect) const SIP_HOLDGIL
Returns true when rectangle contains other rectangle.
Definition: qgsrectangle.h:363
A class to represent a vector.
Definition: qgsvector.h:30
double y() const SIP_HOLDGIL
Returns the vector's y-component.
Definition: qgsvector.h:156
double x() const SIP_HOLDGIL
Returns the vector's x-component.
Definition: qgsvector.h:147
double length() const SIP_HOLDGIL
Returns the length of the vector.
Definition: qgsvector.h:128
static Qgis::GeometryType geometryType(Qgis::WkbType type) SIP_HOLDGIL
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
Definition: qgswkbtypes.h:865
static Qgis::WkbType flatType(Qgis::WkbType type) SIP_HOLDGIL
Returns the flat type for a WKB type.
Definition: qgswkbtypes.h:629
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:4192
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:3988
#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