QGIS API Documentation  3.6.0-Noosa (5873452)
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  const QgsPolygon *polygonSimplifiedData = qgsgeometry_cast<const QgsPolygon *>( polygonSimplified.constGet() );
427  if ( _minimum_distance_between_coordinates( *polygonSimplifiedData ) < 0.001 )
428  {
429  // Failed to fix that. It could be a really tiny geometry... or maybe they gave us
430  // geometry in unprojected lat/lon coordinates
431  QgsMessageLog::logMessage( QObject::tr( "geometry's coordinates are too close to each other and simplification failed - skipping" ), QObject::tr( "3D" ) );
432  return;
433  }
434  else
435  {
436  polygonNew.reset( polygonSimplifiedData->clone() );
437  }
438  }
439 
440  if ( !_check_intersecting_rings( *polygonNew ) )
441  {
442  // skip the polygon - it would cause a crash inside poly2tri library
443  QgsMessageLog::logMessage( QObject::tr( "polygon rings self-intersect or intersect each other - skipping" ), QObject::tr( "3D" ) );
444  return;
445  }
446 
447  QList< std::vector<p2t::Point *> > polylinesToDelete;
448  QHash<p2t::Point *, float> z;
449 
450  // polygon exterior
451  std::vector<p2t::Point *> polyline;
452  _ringToPoly2tri( polygonNew->exteriorRing(), polyline, z );
453  polylinesToDelete << polyline;
454 
455  std::unique_ptr<p2t::CDT> cdt( new p2t::CDT( polyline ) );
456 
457  // polygon holes
458  for ( int i = 0; i < polygonNew->numInteriorRings(); ++i )
459  {
460  std::vector<p2t::Point *> holePolyline;
461  const QgsCurve *hole = polygonNew->interiorRing( i );
462 
463  _ringToPoly2tri( hole, holePolyline, z );
464 
465  cdt->AddHole( holePolyline );
466  polylinesToDelete << holePolyline;
467  }
468 
469  // run triangulation and write vertices to the output data array
470  try
471  {
472  cdt->Triangulate();
473 
474  std::vector<p2t::Triangle *> triangles = cdt->GetTriangles();
475 
476  for ( size_t i = 0; i < triangles.size(); ++i )
477  {
478  p2t::Triangle *t = triangles[i];
479  for ( int j = 0; j < 3; ++j )
480  {
481  p2t::Point *p = t->GetPoint( j );
482  QVector4D pt( p->x, p->y, z[p], 0 );
483  if ( toOldBase )
484  pt = *toOldBase * pt;
485  const double fx = pt.x() - mOriginX + pt0.x();
486  const double fy = pt.y() - mOriginY + pt0.y();
487  const double fz = pt.z() + extrusionHeight + pt0.z();
488  mData << fx << fz << -fy;
489  if ( mAddNormals )
490  mData << pNormal.x() << pNormal.z() << - pNormal.y();
491  }
492 
493  if ( mAddBackFaces )
494  {
495  // the same triangle with reversed order of coordinates and inverted normal
496  for ( int j = 2; j >= 0; --j )
497  {
498  p2t::Point *p = t->GetPoint( j );
499  QVector4D pt( p->x, p->y, z[p], 0 );
500  if ( toOldBase )
501  pt = *toOldBase * pt;
502  const double fx = pt.x() - mOriginX + pt0.x();
503  const double fy = pt.y() - mOriginY + pt0.y();
504  const double fz = pt.z() + extrusionHeight + pt0.z();
505  mData << fx << fz << -fy;
506  if ( mAddNormals )
507  mData << -pNormal.x() << -pNormal.z() << pNormal.y();
508  }
509  }
510  }
511  }
512  catch ( ... )
513  {
514  QgsMessageLog::logMessage( QObject::tr( "Triangulation failed. Skipping polygon…" ), QObject::tr( "3D" ) );
515  }
516 
517  for ( int i = 0; i < polylinesToDelete.count(); ++i )
518  qDeleteAll( polylinesToDelete[i] );
519  }
520 
521  // add walls if extrusion is enabled
522  if ( extrusionHeight != 0 )
523  {
524  _makeWalls( *exterior, false, extrusionHeight, mData, mAddNormals, mOriginX, mOriginY );
525 
526  for ( int i = 0; i < polygon.numInteriorRings(); ++i )
527  _makeWalls( *polygon.interiorRing( i ), true, extrusionHeight, mData, mAddNormals, mOriginX, mOriginY );
528  }
529 }
530 
531 QgsPoint getPointFromData( QVector< float >::const_iterator &it )
532 {
533  // tessellator geometry is x, z, -y
534  double x = *it;
535  ++it;
536  double z = *it;
537  ++it;
538  double y = -( *it );
539  ++it;
540  return QgsPoint( x, y, z );
541 }
542 
544 {
545  return mData.size() / ( mAddNormals ? 6 : 3 );
546 }
547 
548 std::unique_ptr<QgsMultiPolygon> QgsTessellator::asMultiPolygon() const
549 {
550  std::unique_ptr< QgsMultiPolygon > mp = qgis::make_unique< QgsMultiPolygon >();
551  const QVector<float> data = mData;
552  for ( auto it = data.constBegin(); it != data.constEnd(); )
553  {
554  QgsPoint p1 = getPointFromData( it );
555  QgsPoint p2 = getPointFromData( it );
556  QgsPoint p3 = getPointFromData( it );
557  mp->addGeometry( new QgsTriangle( p1, p2, p3 ) );
558  }
559  return mp;
560 }
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:106
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:770
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