QGIS API Documentation  3.25.0-Master (10b47c2603)
qgsgeometryvalidator.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsgeometryvalidator.cpp - geometry validation thread
3  -------------------------------------------------------------------
4 Date : 03.01.2012
5 Copyright : (C) 2012 by Juergen E. Fischer
6 email : 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"
17 #include "qgsgeometryvalidator.h"
18 #include "qgsgeometry.h"
19 #include "qgslogger.h"
20 #include "qgsgeos.h"
21 #include "qgsgeometrycollection.h"
22 #include "qgspolygon.h"
23 #include "qgscurvepolygon.h"
24 #include "qgscurve.h"
25 #include "qgsvertexid.h"
26 
27 QgsGeometryValidator::QgsGeometryValidator( const QgsGeometry &geometry, QVector<QgsGeometry::Error> *errors, Qgis::GeometryValidationEngine method )
28  : mGeometry( geometry )
29  , mErrors( errors )
30  , mStop( false )
31  , mErrorCount( 0 )
32  , mMethod( method )
33 {
34 }
35 
37 {
38  stop();
39  wait();
40 }
41 
43 {
44  mStop = true;
45 }
46 
47 void QgsGeometryValidator::checkRingIntersections( int partIndex0, int ringIndex0, const QgsCurve *ring0, int partIndex1, int ringIndex1, const QgsCurve *ring1 )
48 {
49  const QgsLineString *ringLine0 = qgsgeometry_cast< const QgsLineString * >( ring0 );
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 
106 void 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  for ( int j = 0; !mStop && j < line->numPoints() - 3; j++ )
192  {
193  const double xAtJ = line->xAt( j );
194  const double yAtJ = line->yAt( j );
195  const QgsVector v( line->xAt( j + 1 ) - xAtJ, line->yAt( j + 1 ) - yAtJ );
196  const double vl = v.length();
197 
198  const int n = ( j == 0 && ring ) ? line->numPoints() - 2 : line->numPoints() - 1;
199 
200  for ( int k = j + 2; !mStop && k < n; k++ )
201  {
202  const double xAtK = line->xAt( k );
203  const double yAtK = line->yAt( k );
204 
205  const QgsVector w( line->xAt( k + 1 ) - xAtK, line->yAt( k + 1 ) - yAtK );
206 
207  double sX;
208  double sY;
209  if ( !intersectLines( xAtJ, yAtJ, v, xAtK, yAtK, w, sX, sY ) )
210  continue;
211 
212  double d = 0.0;
213  try
214  {
215  d = -distLine2Point( xAtJ, yAtJ, v.perpVector(), sX, sY );
216  }
217  catch ( QgsException &e )
218  {
219  Q_UNUSED( e )
220  QgsDebugMsg( "Error validating: " + e.what() );
221  continue;
222  }
223  if ( d < 0 || d > vl )
224  continue;
225 
226  try
227  {
228  d = -distLine2Point( xAtK, yAtK, w.perpVector(), sX, sY );
229  }
230  catch ( QgsException &e )
231  {
232  Q_UNUSED( e )
233  QgsDebugMsg( "Error validating: " + e.what() );
234  continue;
235  }
236 
237  if ( d <= 0 || d >= w.length() )
238  continue;
239 
240  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 );
241  QgsDebugMsgLevel( msg, 2 );
242  emit errorFound( QgsGeometry::Error( msg, QgsPointXY( sX, sY ) ) );
243  mErrorCount++;
244  }
245  }
246 }
247 
248 void QgsGeometryValidator::validatePolygon( int partIndex, const QgsCurvePolygon *polygon )
249 {
250  // check if holes are inside polygon
251  for ( int i = 0; !mStop && i < polygon->numInteriorRings(); ++i )
252  {
253  if ( !ringInRing( polygon->interiorRing( i ), polygon->exteriorRing() ) )
254  {
255  const QString msg = QObject::tr( "ring %1 of polygon %2 not in exterior ring" ).arg( i + 1 ).arg( partIndex );
256  QgsDebugMsg( msg );
257  emit errorFound( QgsGeometry::Error( msg ) );
258  mErrorCount++;
259  }
260  }
261 
262  // check holes for intersections
263  for ( int i = 0; !mStop && i < polygon->numInteriorRings(); i++ )
264  {
265  for ( int j = i + 1; !mStop && j < polygon->numInteriorRings(); j++ )
266  {
267  checkRingIntersections( partIndex, i + 1, polygon->interiorRing( i ),
268  partIndex, j + 1, polygon->interiorRing( j ) );
269  }
270  }
271 
272  // check if rings are self-intersecting
273  validatePolyline( 0, qgsgeometry_cast< const QgsLineString * >( polygon->exteriorRing() ), true );
274  for ( int i = 0; !mStop && i < polygon->numInteriorRings(); i++ )
275  {
276  validatePolyline( i + 1, qgsgeometry_cast< const QgsLineString * >( polygon->interiorRing( i ) ), true );
277  }
278 }
279 
281 {
282  mErrorCount = 0;
283  if ( mGeometry.isNull() )
284  {
285  return;
286  }
287 
288  switch ( mMethod )
289  {
290  case Qgis::GeometryValidationEngine::Geos:
291  {
292  // avoid calling geos for trivial point geometries
294  {
295  return;
296  }
297 
298  const QgsGeos geos( mGeometry.constGet() );
299  QString error;
300  QgsGeometry errorLoc;
301  if ( !geos.isValid( &error, true, &errorLoc ) )
302  {
303  if ( errorLoc.isNull() )
304  {
305  emit errorFound( QgsGeometry::Error( error ) );
306  mErrorCount++;
307  }
308  else
309  {
310  const QgsPointXY point = errorLoc.asPoint();
311  emit errorFound( QgsGeometry::Error( error, point ) );
312  mErrorCount++;
313  }
314  }
315 
316  break;
317  }
318 
319  case Qgis::GeometryValidationEngine::QgisInternal:
320  {
321  switch ( QgsWkbTypes::flatType( mGeometry.constGet()->wkbType() ) )
322  {
323  case QgsWkbTypes::Point:
325  break;
326 
328  validatePolyline( 0, qgsgeometry_cast< const QgsLineString * >( mGeometry.constGet() ) );
329  break;
330 
332  {
333  const QgsGeometryCollection *collection = qgsgeometry_cast< const QgsGeometryCollection * >( mGeometry.constGet() );
334  for ( int i = 0; !mStop && i < collection->numGeometries(); i++ )
335  validatePolyline( i, qgsgeometry_cast< const QgsLineString * >( collection->geometryN( i ) ) );
336  break;
337  }
338 
341  validatePolygon( 0, qgsgeometry_cast< const QgsCurvePolygon * >( mGeometry.constGet() ) );
342  break;
343 
346  {
347  const QgsGeometryCollection *collection = qgsgeometry_cast< const QgsGeometryCollection * >( mGeometry.constGet() );
348  for ( int i = 0; !mStop && i < collection->numGeometries(); i++ )
349  validatePolygon( i, qgsgeometry_cast< const QgsCurvePolygon * >( collection->geometryN( i ) ) );
350 
351  for ( int i = 0; !mStop && i < collection->numGeometries(); i++ )
352  {
353  const QgsCurvePolygon *poly = qgsgeometry_cast< const QgsCurvePolygon * >( collection->geometryN( i ) );
354  if ( !poly->exteriorRing() || poly->exteriorRing()->isEmpty() )
355  {
356  emit errorFound( QgsGeometry::Error( QObject::tr( "Polygon %1 has no rings" ).arg( i ) ) );
357  mErrorCount++;
358  continue;
359  }
360 
361  for ( int j = i + 1; !mStop && j < collection->numGeometries(); j++ )
362  {
363  const QgsCurvePolygon *poly2 = qgsgeometry_cast< const QgsCurvePolygon * >( collection->geometryN( j ) );
364  if ( !poly2->exteriorRing() || poly2->exteriorRing()->isEmpty() )
365  continue;
366 
367  if ( ringInRing( poly->exteriorRing(),
368  poly2->exteriorRing() ) )
369  {
370  emit errorFound( QgsGeometry::Error( QObject::tr( "Polygon %1 lies inside polygon %2" ).arg( i ).arg( j ) ) );
371  mErrorCount++;
372  }
373  else if ( ringInRing( poly2->exteriorRing(),
374  poly->exteriorRing() ) )
375  {
376  emit errorFound( QgsGeometry::Error( QObject::tr( "Polygon %1 lies inside polygon %2" ).arg( j ).arg( i ) ) );
377  mErrorCount++;
378  }
379  else
380  {
381  checkRingIntersections( i, 0, poly->exteriorRing(),
382  j, 0, poly2->exteriorRing() );
383  }
384  }
385  }
386  break;
387  }
388 
390  {
391  emit errorFound( QgsGeometry::Error( QObject::tr( "Unknown geometry type %1" ).arg( mGeometry.wkbType() ) ) );
392  mErrorCount++;
393  break;
394  }
395 
396  default:
397  break;
398  }
399 
400  if ( mStop )
401  {
402  emit validationFinished( QObject::tr( "Geometry validation was aborted." ) );
403  }
404  else if ( mErrorCount > 0 )
405  {
406  emit validationFinished( QObject::tr( "Geometry has %n error(s).", nullptr, mErrorCount ) );
407  }
408  else
409  {
410  emit validationFinished( QObject::tr( "Geometry is valid." ) );
411  }
412  break;
413  }
414  }
415 }
416 
418 {
419  if ( mErrors )
420  *mErrors << e;
421 }
422 
423 void QgsGeometryValidator::validateGeometry( const QgsGeometry &geometry, QVector<QgsGeometry::Error> &errors, Qgis::GeometryValidationEngine method )
424 {
425  QgsGeometryValidator *gv = new QgsGeometryValidator( geometry, &errors, method );
427  gv->run();
428  gv->wait();
429 }
430 
431 //
432 // distance of point q from line through p in direction v
433 // return >0 => q lies left of the line
434 // <0 => q lies right of the line
435 //
436 double QgsGeometryValidator::distLine2Point( double px, double py, QgsVector v, double qX, double qY )
437 {
438  const double l = v.length();
439  if ( qgsDoubleNear( l, 0 ) )
440  {
441  throw QgsException( QObject::tr( "invalid line" ) );
442  }
443 
444  return ( v.x() * ( qY - py ) - v.y() * ( qX - px ) ) / l;
445 }
446 
447 bool QgsGeometryValidator::intersectLines( double px, double py, QgsVector v, double qx, double qy, QgsVector w, double &sX, double &sY )
448 {
449  const double d = v.y() * w.x() - v.x() * w.y();
450 
451  if ( qgsDoubleNear( d, 0 ) )
452  return false;
453 
454  const double dx = qx - px;
455  const double dy = qy - py;
456  const double k = ( dy * w.x() - dx * w.y() ) / d;
457 
458  sX = px + v.x() * k;
459  sY = py + v.y() * k;
460 
461  return true;
462 }
463 
464 bool QgsGeometryValidator::pointInRing( const QgsCurve *ring, double pX, double pY )
465 {
466  if ( !ring->boundingBox().contains( pX, pY ) )
467  return false;
468 
469  bool inside = false;
470  int j = ring->numPoints() - 1;
471 
472  for ( int i = 0; !mStop && i < ring->numPoints(); i++ )
473  {
474  const double xAti = ring->xAt( i );
475  const double yAti = ring->yAt( i );
476  const double xAtj = ring->xAt( j );
477  const double yAtj = ring->yAt( j );
478 
479  if ( qgsDoubleNear( xAti, pX ) && qgsDoubleNear( yAti, pY ) )
480  return true;
481 
482  if ( ( yAti < pY && yAtj >= pY ) ||
483  ( yAtj < pY && yAti >= pY ) )
484  {
485  if ( xAti + ( pY - yAti ) / ( yAtj - yAti ) * ( xAtj - xAti ) <= pX )
486  inside = !inside;
487  }
488 
489  j = i;
490  }
491 
492  return inside;
493 }
494 
495 bool QgsGeometryValidator::ringInRing( const QgsCurve *inside, const QgsCurve *outside )
496 {
497  if ( !outside->boundingBox().contains( inside->boundingBox() ) )
498  return false;
499 
500  for ( int i = 0; !mStop && i < inside->numPoints(); i++ )
501  {
502  if ( !pointInRing( outside, inside->xAt( i ), inside->yAt( i ) ) )
503  return false;
504  }
505 
506  return true;
507 }
GeometryValidationEngine
Available engines for validating geometries.
Definition: qgis.h:805
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.
QgsWkbTypes::Type 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:48
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:2405
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:125
const QgsAbstractGeometry * constGet() const SIP_HOLDGIL
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
QgsWkbTypes::Type wkbType() const SIP_HOLDGIL
Returns type of the geometry as a WKB type (point / linestring / polygon etc.)
Q_GADGET bool isNull
Definition: qgsgeometry.h:127
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:104
Line string geometry type, with support for z-dimension and m-values.
Definition: qgslinestring.h:44
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 GeometryType geometryType(Type type) SIP_HOLDGIL
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
Definition: qgswkbtypes.h:968
static Type flatType(Type type) SIP_HOLDGIL
Returns the flat type for a WKB type.
Definition: qgswkbtypes.h:732
Contains geos related utilities and functions.
Definition: qgsgeos.h:42
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:2062
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(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