32#include <unordered_set>
34static std::pair<float, float> rotateCoords(
float x,
float y,
float origin_x,
float origin_y,
float r )
36 r = qDegreesToRadians( r );
37 float x0 = x - origin_x, y0 = y - origin_y;
41 const float x1 = origin_x + x0 * qCos( r ) - y0 * qSin( r );
42 const float y1 = origin_y + x0 * qSin( r ) + y0 * qCos( r );
43 return std::make_pair( x1, y1 );
46static void make_quad(
float x0,
float y0,
float z0,
float x1,
float y1,
float z1,
float height, QVector<float> &data,
bool addNormals,
bool addTextureCoords,
float textureRotation,
bool zUp )
48 const float dx = x1 - x0;
49 const float dy = y1 - y0;
52 QVector3D vn = zUp ? QVector3D( -dy, dx, 0 ) : QVector3D( -dy, 0, -dx );
60 QVector<double> textureCoordinates;
61 textureCoordinates.reserve( 12 );
63 if ( fabsf( dy ) <= fabsf( dx ) )
94 textureCoordinates.push_back( u0 );
95 textureCoordinates.push_back( v0 );
97 textureCoordinates.push_back( u1 );
98 textureCoordinates.push_back( v1 );
100 textureCoordinates.push_back( u2 );
101 textureCoordinates.push_back( v2 );
103 textureCoordinates.push_back( u2 );
104 textureCoordinates.push_back( v2 );
106 textureCoordinates.push_back( u1 );
107 textureCoordinates.push_back( v1 );
109 textureCoordinates.push_back( u3 );
110 textureCoordinates.push_back( v3 );
112 for (
int i = 0; i < textureCoordinates.size(); i += 2 )
114 const std::pair<float, float> rotated = rotateCoords( textureCoordinates[i], textureCoordinates[i + 1], 0, 0, textureRotation );
115 textureCoordinates[i] = rotated.first;
116 textureCoordinates[i + 1] = rotated.second;
122 data << x0 << y0 << z0 + height;
124 data << x0 << z0 + height << -y0;
126 data << vn.x() << vn.y() << vn.z();
127 if ( addTextureCoords )
128 data << textureCoordinates[0] << textureCoordinates[1];
131 data << x1 << y1 << z1 + height;
133 data << x1 << z1 + height << -y1;
135 data << vn.x() << vn.y() << vn.z();
136 if ( addTextureCoords )
137 data << textureCoordinates[2] << textureCoordinates[3];
140 data << x0 << y0 << z0;
142 data << x0 << z0 << -y0;
144 data << vn.x() << vn.y() << vn.z();
145 if ( addTextureCoords )
146 data << textureCoordinates[4] << textureCoordinates[5];
151 data << x0 << y0 << z0;
153 data << x0 << z0 << -y0;
155 data << vn.x() << vn.y() << vn.z();
156 if ( addTextureCoords )
157 data << textureCoordinates[6] << textureCoordinates[7];
160 data << x1 << y1 << z1 + height;
162 data << x1 << z1 + height << -y1;
164 data << vn.x() << vn.y() << vn.z();
165 if ( addTextureCoords )
166 data << textureCoordinates[8] << textureCoordinates[9];
169 data << x1 << y1 << z1;
171 data << x1 << z1 << -y1;
173 data << vn.x() << vn.y() << vn.z();
174 if ( addTextureCoords )
175 data << textureCoordinates[10] << textureCoordinates[11];
180 bool addTextureCoords,
int facade,
float textureRotation )
181 : mOriginX( originX )
182 , mOriginY( originY )
183 , mAddNormals( addNormals )
184 , mInvertNormals( invertNormals )
185 , mAddBackFaces( addBackFaces )
186 , mAddTextureCoords( addTextureCoords )
188 , mTessellatedFacade( facade )
189 , mTextureRotation( textureRotation )
195 bool addTextureCoords,
int facade,
float textureRotation )
197 , mOriginX( mBounds.xMinimum() )
198 , mOriginY( mBounds.yMinimum() )
199 , mAddNormals( addNormals )
200 , mInvertNormals( invertNormals )
201 , mAddBackFaces( addBackFaces )
202 , mAddTextureCoords( addTextureCoords )
204 , mTessellatedFacade( facade )
205 , mTextureRotation( textureRotation )
210void QgsTessellator::init()
212 mStride = 3 *
sizeof( float );
214 mStride += 3 *
sizeof( float );
215 if ( mAddTextureCoords )
216 mStride += 2 *
sizeof( float );
219static bool _isRingCounterClockWise(
const QgsCurve &ring )
226 for (
int i = 1; i < count + 1; ++i )
228 ring.
pointAt( i % count, pt, vt );
229 a += ptPrev.
x() * pt.
y() - ptPrev.
y() * pt.
x();
235static void _makeWalls(
const QgsLineString &ring,
bool ccw,
float extrusionHeight, QVector<float> &data,
236 bool addNormals,
bool addTextureCoords,
double originX,
double originY,
float textureRotation,
bool zUp )
241 const bool is_counter_clockwise = _isRingCounterClockWise( ring );
244 QgsPoint ptPrev = ring.
pointN( is_counter_clockwise == ccw ? 0 : ring.numPoints() - 1 );
245 for (
int i = 1; i < ring.
numPoints(); ++i )
247 pt = ring.
pointN( is_counter_clockwise == ccw ? i : ring.numPoints() - i - 1 );
248 float x0 = ptPrev.
x() - originX, y0 = ptPrev.
y() - originY;
249 float x1 = pt.
x() - originX, y1 = pt.
y() - originY;
250 const float z0 = std::isnan( ptPrev.
z() ) ? 0 : ptPrev.
z();
251 const float z1 = std::isnan( pt.
z() ) ? 0 : pt.
z();
254 make_quad( x0, y0, z0, x1, y1, z1, extrusionHeight, data, addNormals, addTextureCoords, textureRotation, zUp );
259static QVector3D _calculateNormal(
const QgsLineString *curve,
double originX,
double originY,
bool invertNormal,
float extrusionHeight )
264 if ( extrusionHeight != 0 )
265 return QVector3D( 0, 0, 1 );
268 float orientation = 1.f;
270 orientation = -orientation;
272 orientation = -orientation;
273 return QVector3D( 0, 0, orientation );
281 for (
int i = 1; i < curve->
numPoints(); i++ )
284 if ( pt1.
z() != pt2.
z() )
290 if ( sameZ && extrusionHeight != 0 )
291 return QVector3D( 0, 0, 1 );
298 double nx = 0, ny = 0, nz = 0;
301 pt1.
setX( pt1.
x() - originX );
302 pt1.
setY( pt1.
y() - originY );
303 for (
int i = 1; i < curve->
numPoints(); i++ )
306 pt2.
setX( pt2.
x() - originX );
307 pt2.
setY( pt2.
y() - originY );
309 if ( std::isnan( pt1.
z() ) || std::isnan( pt2.
z() ) )
312 nx += ( pt1.
y() - pt2.
y() ) * ( pt1.
z() + pt2.
z() );
313 ny += ( pt1.
z() - pt2.
z() ) * ( pt1.
x() + pt2.
x() );
314 nz += ( pt1.
x() - pt2.
x() ) * ( pt1.
y() + pt2.
y() );
319 QVector3D normal( nx, ny, nz );
327static void _normalVectorToXYVectors(
const QVector3D &pNormal, QVector3D &pXVector, QVector3D &pYVector )
332 if ( pNormal.z() > 0.001 || pNormal.z() < -0.001 )
334 pXVector = QVector3D( 1, 0, -pNormal.x() / pNormal.z() );
336 else if ( pNormal.y() > 0.001 || pNormal.y() < -0.001 )
338 pXVector = QVector3D( 1, -pNormal.x() / pNormal.y(), 0 );
342 pXVector = QVector3D( -pNormal.y() / pNormal.x(), 1, 0 );
344 pXVector.normalize();
345 pYVector = QVector3D::normal( pNormal, pXVector );
350 std::size_t
operator()(
const std::pair<float, float> pair )
const
352 const std::size_t h1 = std::hash<float>()( pair.first );
353 const std::size_t h2 = std::hash<float>()( pair.second );
350 std::size_t
operator()(
const std::pair<float, float> pair )
const {
…}
359static void _ringToPoly2tri(
const QgsLineString *ring, std::vector<p2t::Point *> &polyline, QHash<p2t::Point *, float> *zHash )
363 polyline.reserve( pCount );
365 const double *srcXData = ring->
xData();
366 const double *srcYData = ring->
yData();
367 const double *srcZData = ring->
zData();
368 std::unordered_set<std::pair<float, float>,
float_pair_hash> foundPoints;
370 for (
int i = 0; i < pCount - 1; ++i )
372 const float x = *srcXData++;
373 const float y = *srcYData++;
375 const auto res = foundPoints.insert( std::make_pair( x, y ) );
382 p2t::Point *pt2 =
new p2t::Point( x, y );
383 polyline.push_back( pt2 );
386 ( *zHash )[pt2] = *srcZData++;
394 const double exp = 1e10;
395 return round( x * exp ) / exp;
399static QgsCurve *_transform_ring_to_new_base(
const QgsLineString &curve,
const QgsPoint &pt0,
const QMatrix4x4 *toNewBase,
const float scale )
408 double *xData = x.data();
409 double *yData = y.data();
410 double *zData = z.data();
412 const double *srcXData = curve.
xData();
413 const double *srcYData = curve.
yData();
414 const double *srcZData = curve.
is3D() ? curve.
zData() :
nullptr;
416 for (
int i = 0; i < count; ++i )
418 QVector4D v( *srcXData++ - pt0.
x(),
419 *srcYData++ - pt0.
y(),
420 srcZData ? *srcZData++ - pt0.
z() : 0,
423 v = toNewBase->map( v );
426 v.setX( v.x() * scale );
427 v.setY( v.y() * scale );
447static QgsPolygon *_transform_polygon_to_new_base(
const QgsPolygon &polygon,
const QgsPoint &pt0,
const QMatrix4x4 *toNewBase,
const float scale )
450 p->
setExteriorRing( _transform_ring_to_new_base( *qgsgeometry_cast< const QgsLineString * >( polygon.
exteriorRing() ), pt0, toNewBase, scale ) );
452 p->
addInteriorRing( _transform_ring_to_new_base( *qgsgeometry_cast< const QgsLineString * >( polygon.
interiorRing( i ) ), pt0, toNewBase, scale ) );
461 std::vector< const QgsLineString * > rings;
463 rings.emplace_back( qgsgeometry_cast< const QgsLineString * >( polygon.
exteriorRing() ) );
465 rings.emplace_back( qgsgeometry_cast< const QgsLineString * >( polygon.
interiorRing( i ) ) );
470 if ( numPoints <= 1 )
473 const double *srcXData = ring->
xData();
474 const double *srcYData = ring->
yData();
475 double x0 = *srcXData++;
476 double y0 = *srcYData++;
477 for (
int i = 1; i < numPoints; ++i )
479 const double x1 = *srcXData++;
480 const double y1 = *srcYData++;
489 return min_d != 1e20 ? std::sqrt( min_d ) : 1e20;
498 const QVector3D pNormal = !mNoZ ? _calculateNormal( exterior, mOriginX, mOriginY, mInvertNormals, extrusionHeight ) : QVector3D();
499 const int pCount = exterior->
numPoints();
503 float zMin = std::numeric_limits<float>::max();
504 float zMaxBase = -std::numeric_limits<float>::max();
505 float zMaxExtruded = -std::numeric_limits<float>::max();
507 const float scale = mBounds.
isNull() ? 1.0 : std::max( 10000.0 / mBounds.
width(), 10000.0 / mBounds.
height() );
509 std::unique_ptr<QMatrix4x4> toNewBase, toOldBase;
511 std::unique_ptr<QgsPolygon> polygonNew;
512 auto rotatePolygonToXYPlane = [&]()
514 if ( !mNoZ && pNormal != QVector3D( 0, 0, 1 ) )
518 QVector3D pXVector, pYVector;
519 _normalVectorToXYVectors( pNormal, pXVector, pYVector );
524 toNewBase.reset(
new QMatrix4x4(
525 pXVector.x(), pXVector.y(), pXVector.z(), 0,
526 pYVector.x(), pYVector.y(), pYVector.z(), 0,
527 pNormal.x(), pNormal.y(), pNormal.z(), 0,
531 toOldBase.reset(
new QMatrix4x4( toNewBase->transposed() ) );
540 polygonNew.reset( _transform_polygon_to_new_base( polygon, pt0, toNewBase.get(), scale ) );
546 const QVector3D upVector( 0, 0, 1 );
547 const float pNormalUpVectorDotProduct = QVector3D::dotProduct( upVector, pNormal );
548 const float radsBetweenUpNormal =
static_cast<float>( qAcos( pNormalUpVectorDotProduct ) );
550 const float detectionDelta = qDegreesToRadians( 10.0f );
552 if ( ( radsBetweenUpNormal > M_PI_2 - detectionDelta && radsBetweenUpNormal < M_PI_2 + detectionDelta )
553 || ( radsBetweenUpNormal > - M_PI_2 - detectionDelta && radsBetweenUpNormal < -M_PI_2 + detectionDelta ) )
557 if ( pCount == 4 && polygon.
numInteriorRings() == 0 && ( mTessellatedFacade & facade ) )
560 if ( mAddTextureCoords )
562 rotatePolygonToXYPlane();
563 triangle = qgsgeometry_cast< QgsLineString * >( polygonNew->exteriorRing() );
564 Q_ASSERT( polygonNew->exteriorRing()->numPoints() >= 3 );
568 const double *xData = exterior->
xData();
569 const double *yData = exterior->
yData();
570 const double *zData = !mNoZ ? exterior->
zData() :
nullptr;
571 for (
int i = 0; i < 3; i++ )
573 const float baseHeight = !zData || mNoZ ? 0.0f :
static_cast<float>( * zData );
574 const float z = mNoZ ? 0.0f : ( baseHeight + extrusionHeight );
575 if ( baseHeight < zMin )
577 if ( baseHeight > zMaxBase )
578 zMaxBase = baseHeight;
579 if ( z > zMaxExtruded )
584 mData << static_cast<float>( *xData - mOriginX ) <<
static_cast<float>( *yData - mOriginY ) << z;
586 mData << pNormal.x() << pNormal.y() << pNormal.z();
590 mData << static_cast<float>( *xData - mOriginX ) << z << static_cast<float>( - *yData + mOriginY );
592 mData << pNormal.x() << pNormal.z() << - pNormal.y();
595 if ( mAddTextureCoords )
597 std::pair<float, float> p( triangle->
xAt( i ), triangle->
yAt( i ) );
600 p = rotateCoords( p.first, p.second, 0.0f, 0.0f, mTextureRotation );
602 else if ( facade & 2 )
604 p = rotateCoords( p.first, p.second, 0.0f, 0.0f, mTextureRotation );
606 mData << p.first << p.second;
617 for (
int i = 2; i >= 0; i-- )
621 mData << static_cast<float>( exterior->
xAt( i ) - mOriginX )
622 <<
static_cast<float>( exterior->
yAt( i ) - mOriginY )
623 <<
static_cast<float>( mNoZ ? 0 : exterior->
zAt( i ) );
625 mData << -pNormal.x() << -pNormal.y() << -pNormal.z();
629 mData << static_cast<float>( exterior->
xAt( i ) - mOriginX )
630 <<
static_cast<float>( mNoZ ? 0 : exterior->
zAt( i ) )
631 <<
static_cast<float>( - exterior->
yAt( i ) + mOriginY );
633 mData << -pNormal.x() << -pNormal.z() << pNormal.y();
636 if ( mAddTextureCoords )
638 std::pair<float, float> p( triangle->
xAt( i ), triangle->
yAt( i ) );
641 p = rotateCoords( p.first, p.second, 0.0f, 0.0f, mTextureRotation );
643 else if ( facade & 2 )
645 p = rotateCoords( p.first, p.second, 0.0f, 0.0f, mTextureRotation );
647 mData << p.first << p.second;
652 else if ( mTessellatedFacade & facade )
655 rotatePolygonToXYPlane();
664 if ( polygonSimplified.
isNull() )
666 mError = QObject::tr(
"geometry simplification failed - skipping" );
669 const QgsPolygon *polygonSimplifiedData = qgsgeometry_cast<const QgsPolygon *>( polygonSimplified.
constGet() );
674 mError = QObject::tr(
"geometry's coordinates are too close to each other and simplification failed - skipping" );
679 polygonNew.reset( polygonSimplifiedData->
clone() );
683 QList< std::vector<p2t::Point *> > polylinesToDelete;
684 QHash<p2t::Point *, float> z;
687 std::vector<p2t::Point *> polyline;
688 _ringToPoly2tri( qgsgeometry_cast< const QgsLineString * >( polygonNew->exteriorRing() ), polyline, mNoZ ?
nullptr : &z );
689 polylinesToDelete << polyline;
691 auto cdt = std::make_unique<p2t::CDT>( polyline );
694 for (
int i = 0; i < polygonNew->numInteriorRings(); ++i )
696 std::vector<p2t::Point *> holePolyline;
697 const QgsLineString *hole = qgsgeometry_cast< const QgsLineString *>( polygonNew->interiorRing( i ) );
699 _ringToPoly2tri( hole, holePolyline, mNoZ ?
nullptr : &z );
701 cdt->AddHole( holePolyline );
702 polylinesToDelete << holePolyline;
710 std::vector<p2t::Triangle *> triangles = cdt->GetTriangles();
712 mData.reserve( mData.size() + 3 * triangles.size() * (
stride() /
sizeof( float ) ) );
713 for (
size_t i = 0; i < triangles.size(); ++i )
715 p2t::Triangle *t = triangles[i];
716 for (
int j = 0; j < 3; ++j )
718 p2t::Point *p = t->GetPoint( j );
719 QVector4D pt( p->x, p->y, mNoZ ? 0 : z[p], 0 );
721 pt = *toOldBase * pt;
722 const double fx = ( pt.x() / scale ) - mOriginX + pt0.
x();
723 const double fy = ( pt.y() / scale ) - mOriginY + pt0.
y();
724 const double baseHeight = mNoZ ? 0 : ( pt.z() + pt0.
z() );
725 const double fz = mNoZ ? 0 : ( pt.z() + extrusionHeight + pt0.
z() );
726 if ( baseHeight < zMin )
728 if ( baseHeight > zMaxBase )
729 zMaxBase = baseHeight;
730 if ( fz > zMaxExtruded )
735 mData << static_cast<float>( fx ) <<
static_cast<float>( fy ) <<
static_cast<float>( fz );
737 mData << pNormal.x() << pNormal.y() << pNormal.z();
741 mData << static_cast<float>( fx ) <<
static_cast<float>( fz ) <<
static_cast<float>( -fy );
743 mData << pNormal.x() << pNormal.z() << - pNormal.y();
746 if ( mAddTextureCoords )
748 const std::pair<float, float> pr = rotateCoords( p->x, p->y, 0.0f, 0.0f, mTextureRotation );
749 mData << pr.first << pr.second;
756 for (
int j = 2; j >= 0; --j )
758 p2t::Point *p = t->GetPoint( j );
759 QVector4D pt( p->x, p->y, mNoZ ? 0 : z[p], 0 );
761 pt = *toOldBase * pt;
762 const double fx = ( pt.x() / scale ) - mOriginX + pt0.
x();
763 const double fy = ( pt.y() / scale ) - mOriginY + pt0.
y();
764 const double fz = mNoZ ? 0 : ( pt.z() + extrusionHeight + pt0.
z() );
768 mData << static_cast<float>( fx ) <<
static_cast<float>( fy ) <<
static_cast<float>( fz );
770 mData << -pNormal.x() << -pNormal.y() << -pNormal.z();
774 mData << static_cast<float>( fx ) <<
static_cast<float>( fz ) <<
static_cast<float>( -fy );
776 mData << -pNormal.x() << -pNormal.z() << pNormal.y();
779 if ( mAddTextureCoords )
781 const std::pair<float, float> pr = rotateCoords( p->x, p->y, 0.0f, 0.0f, mTextureRotation );
782 mData << pr.first << pr.second;
788 catch ( std::runtime_error &err )
794 mError = QObject::tr(
"An unknown error occurred" );
797 for (
int i = 0; i < polylinesToDelete.count(); ++i )
798 qDeleteAll( polylinesToDelete[i] );
802 if ( extrusionHeight != 0 && ( mTessellatedFacade & 1 ) )
804 _makeWalls( *exterior,
false, extrusionHeight, mData, mAddNormals, mAddTextureCoords, mOriginX, mOriginY, mTextureRotation, mOutputZUp );
807 _makeWalls( *qgsgeometry_cast< const QgsLineString * >( polygon.
interiorRing( i ) ),
true, extrusionHeight, mData, mAddNormals, mAddTextureCoords, mOriginX, mOriginY, mTextureRotation, mOutputZUp );
809 if ( zMaxBase + extrusionHeight > zMaxExtruded )
810 zMaxExtruded = zMaxBase + extrusionHeight;
815 if ( zMaxExtruded > mZMax )
816 mZMax = zMaxExtruded;
817 if ( zMaxBase > mZMax )
823 return mData.size() / (
stride() /
sizeof( float ) );
828 auto mp = std::make_unique< QgsMultiPolygon >();
829 const auto nVals = mData.size();
830 mp->reserve( nVals / 9 );
831 for (
auto i =
decltype( nVals ) {0}; i + 8 < nVals; i += 9 )
835 const QgsPoint p1( mData[i + 0], mData[i + 1], mData[i + 2] );
836 const QgsPoint p2( mData[i + 3], mData[i + 4], mData[i + 5] );
837 const QgsPoint p3( mData[i + 6], mData[i + 7], mData[i + 8] );
843 const QgsPoint p1( mData[i + 0], -mData[i + 2], mData[i + 1] );
844 const QgsPoint p2( mData[i + 3], -mData[i + 5], mData[i + 4] );
845 const QgsPoint p3( mData[i + 6], -mData[i + 8], mData[i + 7] );
@ Clockwise
Clockwise direction.
VertexType
Types of vertex.
bool is3D() const
Returns true if the geometry is 3D and contains a z-value.
Qgis::WkbType wkbType() const
Returns the WKB type of the geometry.
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.
Qgis::AngularDirection orientation() const
Returns the curve's orientation, e.g.
virtual int numPoints() const =0
Returns the number of points in the curve.
virtual bool pointAt(int node, QgsPoint &point, Qgis::VertexType &type) const =0
Returns the point and vertex id of a point within the curve.
static double sqrDistance2D(double x1, double y1, double x2, double y2)
Returns the squared 2D distance between (x1, y1) and (x2, y2).
A geometry is the spatial representation of a feature.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
QgsGeometry simplify(double tolerance) const
Returns a simplified version of this geometry using a specified tolerance value.
Line string geometry type, with support for z-dimension and m-values.
const double * yData() const
Returns a const pointer to the y vertex data.
const double * xData() const
Returns a const pointer to the x vertex data.
const double * zData() const
Returns a const pointer to the z vertex data, or nullptr if the linestring does not have z values.
QgsPoint startPoint() const override
Returns the starting point of the curve.
int numPoints() const override
Returns the number of points in the curve.
QgsPoint pointN(int i) const
Returns the specified point from inside the line string.
double yAt(int index) const override
Returns the y-coordinate of the specified node in the line string.
double zAt(int index) const override
Returns the z-coordinate of the specified node in the line string.
double xAt(int index) const override
Returns the x-coordinate of the specified node in the line string.
Point geometry type, with support for z-dimension and m-values.
void setY(double y)
Sets the point's y-coordinate.
void setX(double x)
Sets the point's x-coordinate.
void setExteriorRing(QgsCurve *ring) override
Sets the exterior ring of the polygon.
void addInteriorRing(QgsCurve *ring) override
Adds an interior ring to the geometry (takes ownership)
QgsPolygon * clone() const override
Clones the geometry by performing a deep copy.
A rectangle specified with double values.
std::unique_ptr< QgsMultiPolygon > asMultiPolygon() const
Returns the triangulation as a multipolygon geometry.
QgsTessellator(double originX, double originY, bool addNormals, bool invertNormals=false, bool addBackFaces=false, bool noZ=false, bool addTextureCoords=false, int facade=3, float textureRotation=0.0f)
Creates tessellator with a specified origin point of the world (in map coordinates)
int stride() const
Returns size of one vertex entry in bytes.
void addPolygon(const QgsPolygon &polygon, float extrusionHeight)
Tessellates a triangle and adds its vertex entries to the output data array.
int dataVerticesCount() const
Returns the number of vertices stored in the output data array.
static bool hasZ(Qgis::WkbType type)
Tests whether a WKB type contains the z-dimension.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
double _round_coord(double x)
double _minimum_distance_between_coordinates(const QgsPolygon &polygon)
std::size_t operator()(const std::pair< float, float > pair) const