QGIS API Documentation  3.8.0-Zanzibar (11aff65)
qgstessellator.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgstessellator.cpp
3  --------------------------------------
4  Date : July 2017
5  Copyright : (C) 2017 by Martin Dobias
6  Email : wonder dot sk at gmail dot com
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 "qgstessellator.h"
17 
18 #include "qgscurve.h"
19 #include "qgsgeometry.h"
20 #include "qgsmessagelog.h"
21 #include "qgsmultipolygon.h"
22 #include "qgspoint.h"
23 #include "qgspolygon.h"
24 #include "qgstriangle.h"
25 #include "qgis_sip.h"
26 
27 #include "poly2tri.h"
28 
29 #include <QtDebug>
30 #include <QMatrix4x4>
31 #include <QVector3D>
32 #include <algorithm>
33 
34 
35 static void make_quad( float x0, float y0, float z0, float x1, float y1, float z1, float height, QVector<float> &data, bool addNormals )
36 {
37  float dx = x1 - x0;
38  float dy = -( y1 - y0 );
39 
40  // perpendicular vector in plane to [x,y] is [-y,x]
41  QVector3D vn( -dy, 0, dx );
42  vn = -vn;
43  vn.normalize();
44 
45  // triangle 1
46  data << x0 << z0 + height << -y0;
47  if ( addNormals )
48  data << vn.x() << vn.y() << vn.z();
49  data << x1 << z1 + height << -y1;
50  if ( addNormals )
51  data << vn.x() << vn.y() << vn.z();
52  data << x0 << z0 << -y0;
53  if ( addNormals )
54  data << vn.x() << vn.y() << vn.z();
55 
56  // triangle 2
57  data << x0 << z0 << -y0;
58  if ( addNormals )
59  data << vn.x() << vn.y() << vn.z();
60  data << x1 << z1 + height << -y1;
61  if ( addNormals )
62  data << vn.x() << vn.y() << vn.z();
63  data << x1 << z1 << -y1;
64  if ( addNormals )
65  data << vn.x() << vn.y() << vn.z();
66 }
67 
68 
69 QgsTessellator::QgsTessellator( double originX, double originY, bool addNormals, bool invertNormals, bool addBackFaces )
70  : mOriginX( originX )
71  , mOriginY( originY )
72  , mAddNormals( addNormals )
73  , mInvertNormals( invertNormals )
74  , mAddBackFaces( addBackFaces )
75 {
76  mStride = 3 * sizeof( float );
77  if ( addNormals )
78  mStride += 3 * sizeof( float );
79 }
80 
81 
82 static bool _isRingCounterClockWise( const QgsCurve &ring )
83 {
84  double a = 0;
85  int count = ring.numPoints();
87  QgsPoint pt, ptPrev;
88  ring.pointAt( 0, ptPrev, vt );
89  for ( int i = 1; i < count + 1; ++i )
90  {
91  ring.pointAt( i % count, pt, vt );
92  a += ptPrev.x() * pt.y() - ptPrev.y() * pt.x();
93  ptPrev = pt;
94  }
95  return a > 0; // clockwise if a is negative
96 }
97 
98 static void _makeWalls( const QgsCurve &ring, bool ccw, float extrusionHeight, QVector<float> &data, bool addNormals, double originX, double originY )
99 {
100  // we need to find out orientation of the ring so that the triangles we generate
101  // face the right direction
102  // (for exterior we want clockwise order, for holes we want counter-clockwise order)
103  bool is_counter_clockwise = _isRingCounterClockWise( ring );
104 
106  QgsPoint pt;
107 
108  QgsPoint ptPrev;
109  ring.pointAt( is_counter_clockwise == ccw ? 0 : ring.numPoints() - 1, ptPrev, vt );
110  for ( int i = 1; i < ring.numPoints(); ++i )
111  {
112  ring.pointAt( is_counter_clockwise == ccw ? i : ring.numPoints() - i - 1, pt, vt );
113  float x0 = ptPrev.x() - originX, y0 = ptPrev.y() - originY;
114  float x1 = pt.x() - originX, y1 = pt.y() - originY;
115  float z0 = std::isnan( ptPrev.z() ) ? 0 : ptPrev.z();
116  float z1 = std::isnan( pt.z() ) ? 0 : pt.z();
117 
118  // make a quad
119  make_quad( x0, y0, z0, x1, y1, z1, extrusionHeight, data, addNormals );
120  ptPrev = pt;
121  }
122 }
123 
124 static QVector3D _calculateNormal( const QgsCurve *curve, double originX, double originY, bool invertNormal )
125 {
127  QgsPoint pt1, pt2;
128 
129  // if it is just plain 2D curve there is no need to calculate anything
130  // because it will be a flat horizontally oriented patch
131  if ( !QgsWkbTypes::hasZ( curve->wkbType() ) )
132  return QVector3D( 0, 0, 1 );
133 
134  // often we have 3D coordinates, but Z is the same for all vertices
135  // so in order to save calculation and avoid possible issues with order of vertices
136  // (the calculation below may decide that a polygon faces downwards)
137  bool sameZ = true;
138  curve->pointAt( 0, pt1, vt );
139  for ( int i = 1; i < curve->numPoints(); i++ )
140  {
141  curve->pointAt( i, pt2, vt );
142  if ( pt1.z() != pt2.z() )
143  {
144  sameZ = false;
145  break;
146  }
147  }
148  if ( sameZ )
149  return QVector3D( 0, 0, 1 );
150 
151  // Calculate the polygon's normal vector, based on Newell's method
152  // https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal
153  //
154  // Order of vertices is important here as it determines the front/back face of the polygon
155 
156  double nx = 0, ny = 0, nz = 0;
157  for ( int i = 0; i < curve->numPoints() - 1; i++ )
158  {
159  curve->pointAt( i, pt1, vt );
160  curve->pointAt( i + 1, pt2, vt );
161 
162  // shift points by the tessellator's origin - this does not affect normal calculation and it may save us from losing some precision
163  pt1.setX( pt1.x() - originX );
164  pt1.setY( pt1.y() - originY );
165  pt2.setX( pt2.x() - originX );
166  pt2.setY( pt2.y() - originY );
167 
168  if ( std::isnan( pt1.z() ) || std::isnan( pt2.z() ) )
169  continue;
170 
171  nx += ( pt1.y() - pt2.y() ) * ( pt1.z() + pt2.z() );
172  ny += ( pt1.z() - pt2.z() ) * ( pt1.x() + pt2.x() );
173  nz += ( pt1.x() - pt2.x() ) * ( pt1.y() + pt2.y() );
174  }
175 
176  QVector3D normal( nx, ny, nz );
177  if ( invertNormal )
178  normal = -normal;
179  normal.normalize();
180  return normal;
181 }
182 
183 
184 static void _normalVectorToXYVectors( const QVector3D &pNormal, QVector3D &pXVector, QVector3D &pYVector )
185 {
186  // Here we define the two perpendicular vectors that define the local
187  // 2D space on the plane. They will act as axis for which we will
188  // calculate the projection coordinates of a 3D point to the plane.
189  if ( pNormal.z() > 0.001 || pNormal.z() < -0.001 )
190  {
191  pXVector = QVector3D( 1, 0, -pNormal.x() / pNormal.z() );
192  }
193  else if ( pNormal.y() > 0.001 || pNormal.y() < -0.001 )
194  {
195  pXVector = QVector3D( 1, -pNormal.x() / pNormal.y(), 0 );
196  }
197  else
198  {
199  pXVector = QVector3D( -pNormal.y() / pNormal.x(), 1, 0 );
200  }
201  pXVector.normalize();
202  pYVector = QVector3D::normal( pNormal, pXVector );
203 }
204 
205 
206 static void _ringToPoly2tri( const QgsCurve *ring, std::vector<p2t::Point *> &polyline, QHash<p2t::Point *, float> &zHash )
207 {
209  QgsPoint pt;
210 
211  const int pCount = ring->numPoints();
212 
213  polyline.reserve( pCount );
214 
215  for ( int i = 0; i < pCount - 1; ++i )
216  {
217  ring->pointAt( i, pt, vt );
218  const float x = pt.x();
219  const float y = pt.y();
220  const float z = pt.z();
221 
222  const bool found = std::find_if( polyline.begin(), polyline.end(), [x, y]( p2t::Point *&p ) { return *p == p2t::Point( x, y ); } ) != polyline.end();
223 
224  if ( found )
225  {
226  continue;
227  }
228 
229  p2t::Point *pt2 = new p2t::Point( x, y );
230  polyline.push_back( pt2 );
231  zHash[pt2] = z;
232  }
233 }
234 
235 
236 inline double _round_coord( double x )
237 {
238  const double exp = 1e10; // round to 10 decimal digits
239  return round( x * exp ) / exp;
240 }
241 
242 
243 static QgsCurve *_transform_ring_to_new_base( const QgsCurve &curve, const QgsPoint &pt0, const QMatrix4x4 *toNewBase )
244 {
245  int count = curve.numPoints();
246  QVector<QgsPoint> pts;
247  pts.reserve( count );
249  for ( int i = 0; i < count; ++i )
250  {
251  QgsPoint pt;
252  curve.pointAt( i, pt, vt );
253  QgsPoint pt2( QgsWkbTypes::PointZ, pt.x() - pt0.x(), pt.y() - pt0.y(), std::isnan( pt.z() ) ? 0 : pt.z() - pt0.z() );
254  QVector4D v( pt2.x(), pt2.y(), pt2.z(), 0 );
255  if ( toNewBase )
256  v = toNewBase->map( v );
257 
258  // we also round coordinates before passing them to poly2tri triangulation in order to fix possible numerical
259  // stability issues. We had crashes with nearly collinear points where one of the points was off by a tiny bit (e.g. by 1e-20).
260  // See TestQgsTessellator::testIssue17745().
261  //
262  // A hint for a similar issue: https://github.com/greenm01/poly2tri/issues/99
263  //
264  // The collinear tests uses epsilon 1e-12. Seems rounding to 12 places you still
265  // can get problems with this test when points are pretty much on a straight line.
266  // I suggest you round to 10 decimals for stability and you can live with that
267  // precision.
268 
269  pts << QgsPoint( QgsWkbTypes::PointZ, _round_coord( v.x() ), _round_coord( v.y() ), _round_coord( v.z() ) );
270  }
271  return new QgsLineString( pts );
272 }
273 
274 
275 static QgsPolygon *_transform_polygon_to_new_base( const QgsPolygon &polygon, const QgsPoint &pt0, const QMatrix4x4 *toNewBase )
276 {
277  QgsPolygon *p = new QgsPolygon;
278  p->setExteriorRing( _transform_ring_to_new_base( *polygon.exteriorRing(), pt0, toNewBase ) );
279  for ( int i = 0; i < polygon.numInteriorRings(); ++i )
280  p->addInteriorRing( _transform_ring_to_new_base( *polygon.interiorRing( i ), pt0, toNewBase ) );
281  return p;
282 }
283 
284 static bool _check_intersecting_rings( const QgsPolygon &polygon )
285 {
286  QList<QgsGeometry> geomRings;
287  geomRings << QgsGeometry( polygon.exteriorRing()->clone() );
288  for ( int i = 0; i < polygon.numInteriorRings(); ++i )
289  geomRings << QgsGeometry( polygon.interiorRing( i )->clone() );
290 
291  // we need to make sure that the polygon has no rings with self-intersection: that may
292  // crash the tessellator. The original geometry maybe have been valid and the self-intersection
293  // was introduced when transforming to a new base (in a rare case when all points are not in the same plane)
294 
295  for ( int i = 0; i < geomRings.count(); ++i )
296  {
297  if ( !geomRings[i].isSimple() )
298  return false;
299  }
300 
301  // At this point we assume that input polygons are valid according to the OGC definition.
302  // This means e.g. no duplicate points, polygons are simple (no butterfly shaped polygon with self-intersection),
303  // internal rings are inside exterior rings, rings do not cross each other, no dangles.
304 
305  // There is however an issue with polygons where rings touch:
306  // +---+
307  // | |
308  // | +-+-+
309  // | | | |
310  // | +-+ |
311  // | |
312  // +-----+
313  // This is a valid polygon with one exterior and one interior ring that touch at one point,
314  // but poly2tri library does not allow interior rings touch each other or exterior ring.
315  // TODO: Handle the situation better - rather than just detecting the problem, try to fix
316  // it by converting touching rings into one ring.
317 
318  if ( polygon.numInteriorRings() > 0 )
319  {
320  for ( int i = 0; i < geomRings.count(); ++i )
321  for ( int j = i + 1; j < geomRings.count(); ++j )
322  {
323  if ( geomRings[i].intersects( geomRings[j] ) )
324  return false;
325  }
326  }
327  return true;
328 }
329 
330 
332 {
333  double min_d = 1e20;
334  auto it = polygon.vertices_begin();
335 
336  if ( it == polygon.vertices_end() )
337  return min_d;
338 
339  QgsPoint p0 = *it;
340  ++it;
341  for ( ; it != polygon.vertices_end(); ++it )
342  {
343  QgsPoint p1 = *it;
344  double d = p0.distance( p1 );
345  if ( d < min_d )
346  min_d = d;
347  p0 = p1;
348  }
349  return min_d;
350 }
351 
352 
353 void QgsTessellator::addPolygon( const QgsPolygon &polygon, float extrusionHeight )
354 {
355  const QgsCurve *exterior = polygon.exteriorRing();
356 
357  const QVector3D pNormal = _calculateNormal( exterior, mOriginX, mOriginY, mInvertNormals );
358  const int pCount = exterior->numPoints();
359 
360  if ( pCount == 4 && polygon.numInteriorRings() == 0 )
361  {
362  // polygon is a triangle - write vertices to the output data array without triangulation
363  QgsPoint pt;
365  for ( int i = 0; i < 3; i++ )
366  {
367  exterior->pointAt( i, pt, vt );
368  mData << pt.x() - mOriginX << pt.z() << - pt.y() + mOriginY;
369  if ( mAddNormals )
370  mData << pNormal.x() << pNormal.z() << - pNormal.y();
371  }
372 
373  if ( mAddBackFaces )
374  {
375  // the same triangle with reversed order of coordinates and inverted normal
376  for ( int i = 2; i >= 0; i-- )
377  {
378  exterior->pointAt( i, pt, vt );
379  mData << pt.x() - mOriginX << pt.z() << - pt.y() + mOriginY;
380  if ( mAddNormals )
381  mData << -pNormal.x() << -pNormal.z() << pNormal.y();
382  }
383  }
384  }
385  else
386  {
387  if ( !qgsDoubleNear( pNormal.length(), 1, 0.001 ) )
388  return; // this should not happen - pNormal should be normalized to unit length
389 
390  std::unique_ptr<QMatrix4x4> toNewBase, toOldBase;
391  if ( pNormal != QVector3D( 0, 0, 1 ) )
392  {
393  // this is not a horizontal plane - need to reproject the polygon to a new base so that
394  // we can do the triangulation in a plane
395 
396  QVector3D pXVector, pYVector;
397  _normalVectorToXYVectors( pNormal, pXVector, pYVector );
398 
399  // so now we have three orthogonal unit vectors defining new base
400  // let's build transform matrix. We actually need just a 3x3 matrix,
401  // but Qt does not have good support for it, so using 4x4 matrix instead.
402  toNewBase.reset( new QMatrix4x4(
403  pXVector.x(), pXVector.y(), pXVector.z(), 0,
404  pYVector.x(), pYVector.y(), pYVector.z(), 0,
405  pNormal.x(), pNormal.y(), pNormal.z(), 0,
406  0, 0, 0, 0 ) );
407 
408  // our 3x3 matrix is orthogonal, so for inverse we only need to transpose it
409  toOldBase.reset( new QMatrix4x4( toNewBase->transposed() ) );
410  }
411 
412  const QgsPoint ptStart( exterior->startPoint() );
413  const QgsPoint pt0( QgsWkbTypes::PointZ, ptStart.x(), ptStart.y(), std::isnan( ptStart.z() ) ? 0 : ptStart.z() );
414 
415  // subtract ptFirst from geometry for better numerical stability in triangulation
416  // and apply new 3D vector base if the polygon is not horizontal
417  std::unique_ptr<QgsPolygon> polygonNew( _transform_polygon_to_new_base( polygon, pt0, toNewBase.get() ) );
418 
419  if ( _minimum_distance_between_coordinates( *polygonNew ) < 0.001 )
420  {
421  // when the distances between coordinates of input points are very small,
422  // the triangulation likes to crash on numerical errors - when the distances are ~ 1e-5
423  // Assuming that the coordinates should be in a projected CRS, we should be able
424  // to simplify geometries that may cause problems and avoid possible crashes
425  QgsGeometry polygonSimplified = QgsGeometry( polygonNew->clone() ).simplify( 0.001 );
426  if ( polygonSimplified.isNull() )
427  {
428  QgsMessageLog::logMessage( QObject::tr( "geometry simplification failed - skipping" ), QObject::tr( "3D" ) );
429  return;
430  }
431  const QgsPolygon *polygonSimplifiedData = qgsgeometry_cast<const QgsPolygon *>( polygonSimplified.constGet() );
432  if ( _minimum_distance_between_coordinates( *polygonSimplifiedData ) < 0.001 )
433  {
434  // Failed to fix that. It could be a really tiny geometry... or maybe they gave us
435  // geometry in unprojected lat/lon coordinates
436  QgsMessageLog::logMessage( QObject::tr( "geometry's coordinates are too close to each other and simplification failed - skipping" ), QObject::tr( "3D" ) );
437  return;
438  }
439  else
440  {
441  polygonNew.reset( polygonSimplifiedData->clone() );
442  }
443  }
444 
445  if ( !_check_intersecting_rings( *polygonNew ) )
446  {
447  // skip the polygon - it would cause a crash inside poly2tri library
448  QgsMessageLog::logMessage( QObject::tr( "polygon rings self-intersect or intersect each other - skipping" ), QObject::tr( "3D" ) );
449  return;
450  }
451 
452  QList< std::vector<p2t::Point *> > polylinesToDelete;
453  QHash<p2t::Point *, float> z;
454 
455  // polygon exterior
456  std::vector<p2t::Point *> polyline;
457  _ringToPoly2tri( polygonNew->exteriorRing(), polyline, z );
458  polylinesToDelete << polyline;
459 
460  std::unique_ptr<p2t::CDT> cdt( new p2t::CDT( polyline ) );
461 
462  // polygon holes
463  for ( int i = 0; i < polygonNew->numInteriorRings(); ++i )
464  {
465  std::vector<p2t::Point *> holePolyline;
466  const QgsCurve *hole = polygonNew->interiorRing( i );
467 
468  _ringToPoly2tri( hole, holePolyline, z );
469 
470  cdt->AddHole( holePolyline );
471  polylinesToDelete << holePolyline;
472  }
473 
474  // run triangulation and write vertices to the output data array
475  try
476  {
477  cdt->Triangulate();
478 
479  std::vector<p2t::Triangle *> triangles = cdt->GetTriangles();
480 
481  for ( size_t i = 0; i < triangles.size(); ++i )
482  {
483  p2t::Triangle *t = triangles[i];
484  for ( int j = 0; j < 3; ++j )
485  {
486  p2t::Point *p = t->GetPoint( j );
487  QVector4D pt( p->x, p->y, z[p], 0 );
488  if ( toOldBase )
489  pt = *toOldBase * pt;
490  const double fx = pt.x() - mOriginX + pt0.x();
491  const double fy = pt.y() - mOriginY + pt0.y();
492  const double fz = pt.z() + extrusionHeight + pt0.z();
493  mData << fx << fz << -fy;
494  if ( mAddNormals )
495  mData << pNormal.x() << pNormal.z() << - pNormal.y();
496  }
497 
498  if ( mAddBackFaces )
499  {
500  // the same triangle with reversed order of coordinates and inverted normal
501  for ( int j = 2; j >= 0; --j )
502  {
503  p2t::Point *p = t->GetPoint( j );
504  QVector4D pt( p->x, p->y, z[p], 0 );
505  if ( toOldBase )
506  pt = *toOldBase * pt;
507  const double fx = pt.x() - mOriginX + pt0.x();
508  const double fy = pt.y() - mOriginY + pt0.y();
509  const double fz = pt.z() + extrusionHeight + pt0.z();
510  mData << fx << fz << -fy;
511  if ( mAddNormals )
512  mData << -pNormal.x() << -pNormal.z() << pNormal.y();
513  }
514  }
515  }
516  }
517  catch ( ... )
518  {
519  QgsMessageLog::logMessage( QObject::tr( "Triangulation failed. Skipping polygon…" ), QObject::tr( "3D" ) );
520  }
521 
522  for ( int i = 0; i < polylinesToDelete.count(); ++i )
523  qDeleteAll( polylinesToDelete[i] );
524  }
525 
526  // add walls if extrusion is enabled
527  if ( extrusionHeight != 0 )
528  {
529  _makeWalls( *exterior, false, extrusionHeight, mData, mAddNormals, mOriginX, mOriginY );
530 
531  for ( int i = 0; i < polygon.numInteriorRings(); ++i )
532  _makeWalls( *polygon.interiorRing( i ), true, extrusionHeight, mData, mAddNormals, mOriginX, mOriginY );
533  }
534 }
535 
536 QgsPoint getPointFromData( QVector< float >::const_iterator &it )
537 {
538  // tessellator geometry is x, z, -y
539  double x = *it;
540  ++it;
541  double z = *it;
542  ++it;
543  double y = -( *it );
544  ++it;
545  return QgsPoint( x, y, z );
546 }
547 
549 {
550  return mData.size() / ( mAddNormals ? 6 : 3 );
551 }
552 
553 std::unique_ptr<QgsMultiPolygon> QgsTessellator::asMultiPolygon() const
554 {
555  std::unique_ptr< QgsMultiPolygon > mp = qgis::make_unique< QgsMultiPolygon >();
556  const QVector<float> data = mData;
557  for ( auto it = data.constBegin(); it != data.constEnd(); )
558  {
559  QgsPoint p1 = getPointFromData( it );
560  QgsPoint p2 = getPointFromData( it );
561  QgsPoint p3 = getPointFromData( it );
562  mp->addGeometry( new QgsTriangle( p1, p2, p3 ) );
563  }
564  return mp;
565 }
double y
Definition: qgspoint.h:42
double distance(double x, double y) const
Returns the distance between this point and a specified x, y coordinate.
Definition: qgspoint.h:276
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:265
const QgsCurve * interiorRing(int i) const
Retrieves an interior ring from the curve polygon.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:111
int dataVerticesCount() const
Returns the number of vertices stored in the output data array.
virtual bool pointAt(int node, QgsPoint &point, QgsVertexId::VertexType &type) const =0
Returns the point and vertex id of a point within the curve.
std::unique_ptr< QgsMultiPolygon > asMultiPolygon() const
Returns the triangulation as a multipolygon geometry.
QgsPoint getPointFromData(QVector< float >::const_iterator &it)
Triangle geometry type.
Definition: qgstriangle.h:33
static bool hasZ(Type type)
Tests whether a WKB type contains the z-dimension.
Definition: qgswkbtypes.h:771
void addInteriorRing(QgsCurve *ring) override
Adds an interior ring to the geometry (takes ownership)
Definition: qgspolygon.cpp:148
int numInteriorRings() const
Returns the number of interior rings contained with the curve polygon.
double _round_coord(double x)
void addPolygon(const QgsPolygon &polygon, float extrusionHeight)
Tessellates a triangle and adds its vertex entries to the output data array.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
vertex_iterator vertices_end() const
Returns STL-style iterator pointing to the imaginary vertex after the last vertex of the geometry...
T qgsgeometry_cast(const QgsAbstractGeometry *geom)
QgsTessellator(double originX, double originY, bool addNormals, bool invertNormals=false, bool addBackFaces=false)
Creates tessellator with a specified origin point of the world (in map coordinates) ...
Abstract base class for curved geometry type.
Definition: qgscurve.h:35
QgsWkbTypes::Type wkbType() const
Returns the WKB type of the geometry.
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:37
double _minimum_distance_between_coordinates(const QgsPolygon &polygon)
void setX(double x)
Sets the point&#39;s x-coordinate.
Definition: qgspoint.h:213
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
void setY(double y)
Sets the point&#39;s y-coordinate.
Definition: qgspoint.h:224
void setExteriorRing(QgsCurve *ring) override
Sets the exterior ring of the polygon.
Definition: qgspolygon.cpp:179
QgsCurve * clone() const override=0
Clones the geometry by performing a deep copy.
Line string geometry type, with support for z-dimension and m-values.
Definition: qgslinestring.h:43
QgsPolygon * clone() const override
Clones the geometry by performing a deep copy.
Definition: qgspolygon.cpp:42
double z
Definition: qgspoint.h:43
vertex_iterator vertices_begin() const
Returns STL-style iterator pointing to the first vertex of the geometry.
virtual QgsPoint startPoint() const =0
Returns the starting point of the curve.
Polygon geometry type.
Definition: qgspolygon.h:31
const QgsCurve * exteriorRing() const
Returns the curve polygon&#39;s exterior ring.
virtual int numPoints() const =0
Returns the number of points in the curve.
double x
Definition: qgspoint.h:41