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 )
264 return QVector3D( 0, 0, 1 );
272 for (
int i = 1; i < curve->
numPoints(); i++ )
275 if ( pt1.
z() != pt2.
z() )
282 return QVector3D( 0, 0, 1 );
289 double nx = 0, ny = 0, nz = 0;
293 pt1.
setX( pt1.
x() - originX );
294 pt1.
setY( pt1.
y() - originY );
295 for (
int i = 1; i < curve->
numPoints(); i++ )
298 pt2.
setX( pt2.
x() - originX );
299 pt2.
setY( pt2.
y() - originY );
301 if ( std::isnan( pt1.
z() ) || std::isnan( pt2.
z() ) )
304 nx += ( pt1.
y() - pt2.
y() ) * ( pt1.
z() + pt2.
z() );
305 ny += ( pt1.
z() - pt2.
z() ) * ( pt1.
x() + pt2.
x() );
306 nz += ( pt1.
x() - pt2.
x() ) * ( pt1.
y() + pt2.
y() );
311 QVector3D normal( nx, ny, nz );
319static void _normalVectorToXYVectors(
const QVector3D &pNormal, QVector3D &pXVector, QVector3D &pYVector )
324 if ( pNormal.z() > 0.001 || pNormal.z() < -0.001 )
326 pXVector = QVector3D( 1, 0, -pNormal.x() / pNormal.z() );
328 else if ( pNormal.y() > 0.001 || pNormal.y() < -0.001 )
330 pXVector = QVector3D( 1, -pNormal.x() / pNormal.y(), 0 );
334 pXVector = QVector3D( -pNormal.y() / pNormal.x(), 1, 0 );
336 pXVector.normalize();
337 pYVector = QVector3D::normal( pNormal, pXVector );
342 std::size_t
operator()(
const std::pair<float, float> pair )
const
344 const std::size_t h1 = std::hash<float>()( pair.first );
345 const std::size_t h2 = std::hash<float>()( pair.second );
351static void _ringToPoly2tri(
const QgsLineString *ring, std::vector<p2t::Point *> &polyline, QHash<p2t::Point *, float> *zHash )
355 polyline.reserve( pCount );
357 const double *srcXData = ring->
xData();
358 const double *srcYData = ring->
yData();
359 const double *srcZData = ring->
zData();
360 std::unordered_set<std::pair<float, float>,
float_pair_hash> foundPoints;
362 for (
int i = 0; i < pCount - 1; ++i )
364 const float x = *srcXData++;
365 const float y = *srcYData++;
367 const auto res = foundPoints.insert( std::make_pair( x, y ) );
374 p2t::Point *pt2 =
new p2t::Point( x, y );
375 polyline.push_back( pt2 );
378 ( *zHash )[pt2] = *srcZData++;
386 const double exp = 1e10;
387 return round( x * exp ) / exp;
391static QgsCurve *_transform_ring_to_new_base(
const QgsLineString &curve,
const QgsPoint &pt0,
const QMatrix4x4 *toNewBase,
const float scale )
400 double *xData = x.data();
401 double *yData = y.data();
402 double *zData = z.data();
404 const double *srcXData = curve.
xData();
405 const double *srcYData = curve.
yData();
406 const double *srcZData = curve.
is3D() ? curve.
zData() :
nullptr;
408 for (
int i = 0; i < count; ++i )
410 QVector4D v( *srcXData++ - pt0.
x(),
411 *srcYData++ - pt0.
y(),
412 srcZData ? *srcZData++ - pt0.
z() : 0,
415 v = toNewBase->map( v );
418 v.setX( v.x() * scale );
419 v.setY( v.y() * scale );
439static QgsPolygon *_transform_polygon_to_new_base(
const QgsPolygon &polygon,
const QgsPoint &pt0,
const QMatrix4x4 *toNewBase,
const float scale )
442 p->
setExteriorRing( _transform_ring_to_new_base( *qgsgeometry_cast< const QgsLineString * >( polygon.
exteriorRing() ), pt0, toNewBase, scale ) );
444 p->
addInteriorRing( _transform_ring_to_new_base( *qgsgeometry_cast< const QgsLineString * >( polygon.
interiorRing( i ) ), pt0, toNewBase, scale ) );
453 std::vector< const QgsLineString * > rings;
455 rings.emplace_back( qgsgeometry_cast< const QgsLineString * >( polygon.
exteriorRing() ) );
457 rings.emplace_back( qgsgeometry_cast< const QgsLineString * >( polygon.
interiorRing( i ) ) );
462 if ( numPoints <= 1 )
465 const double *srcXData = ring->
xData();
466 const double *srcYData = ring->
yData();
467 double x0 = *srcXData++;
468 double y0 = *srcYData++;
469 for (
int i = 1; i < numPoints; ++i )
471 const double x1 = *srcXData++;
472 const double y1 = *srcYData++;
481 return min_d != 1e20 ? std::sqrt( min_d ) : 1e20;
490 const QVector3D pNormal = !mNoZ ? _calculateNormal( exterior, mOriginX, mOriginY, mInvertNormals ) : QVector3D();
491 const int pCount = exterior->
numPoints();
495 float zMin = std::numeric_limits<float>::max();
496 float zMaxBase = -std::numeric_limits<float>::max();
497 float zMaxExtruded = -std::numeric_limits<float>::max();
499 const float scale = mBounds.
isNull() ? 1.0 : std::max( 10000.0 / mBounds.
width(), 10000.0 / mBounds.
height() );
501 std::unique_ptr<QMatrix4x4> toNewBase, toOldBase;
503 std::unique_ptr<QgsPolygon> polygonNew;
504 auto rotatePolygonToXYPlane = [&]()
506 if ( !mNoZ && pNormal != QVector3D( 0, 0, 1 ) )
510 QVector3D pXVector, pYVector;
511 _normalVectorToXYVectors( pNormal, pXVector, pYVector );
516 toNewBase.reset(
new QMatrix4x4(
517 pXVector.x(), pXVector.y(), pXVector.z(), 0,
518 pYVector.x(), pYVector.y(), pYVector.z(), 0,
519 pNormal.x(), pNormal.y(), pNormal.z(), 0,
523 toOldBase.reset(
new QMatrix4x4( toNewBase->transposed() ) );
532 polygonNew.reset( _transform_polygon_to_new_base( polygon, pt0, toNewBase.get(), scale ) );
538 const QVector3D upVector( 0, 0, 1 );
539 const float pNormalUpVectorDotProduct = QVector3D::dotProduct( upVector, pNormal );
540 const float radsBetweenUpNormal =
static_cast<float>( qAcos( pNormalUpVectorDotProduct ) );
542 const float detectionDelta = qDegreesToRadians( 10.0f );
544 if ( ( radsBetweenUpNormal > M_PI_2 - detectionDelta && radsBetweenUpNormal < M_PI_2 + detectionDelta )
545 || ( radsBetweenUpNormal > - M_PI_2 - detectionDelta && radsBetweenUpNormal < -M_PI_2 + detectionDelta ) )
549 if ( pCount == 4 && polygon.
numInteriorRings() == 0 && ( mTessellatedFacade & facade ) )
552 if ( mAddTextureCoords )
554 rotatePolygonToXYPlane();
555 triangle = qgsgeometry_cast< QgsLineString * >( polygonNew->exteriorRing() );
556 Q_ASSERT( polygonNew->exteriorRing()->numPoints() >= 3 );
560 const double *xData = exterior->
xData();
561 const double *yData = exterior->
yData();
562 const double *zData = !mNoZ ? exterior->
zData() :
nullptr;
563 for (
int i = 0; i < 3; i++ )
565 const float baseHeight = !zData || mNoZ ? 0.0f :
static_cast<float>( * zData );
566 const float z = mNoZ ? 0.0f : ( baseHeight + extrusionHeight );
567 if ( baseHeight < zMin )
569 if ( baseHeight > zMaxBase )
570 zMaxBase = baseHeight;
571 if ( z > zMaxExtruded )
576 mData << static_cast<float>( *xData - mOriginX ) <<
static_cast<float>( *yData - mOriginY ) << z;
578 mData << pNormal.x() << pNormal.y() << pNormal.z();
582 mData << static_cast<float>( *xData - mOriginX ) << z << static_cast<float>( - *yData + mOriginY );
584 mData << pNormal.x() << pNormal.z() << - pNormal.y();
587 if ( mAddTextureCoords )
589 std::pair<float, float> p( triangle->
xAt( i ), triangle->
yAt( i ) );
592 p = rotateCoords( p.first, p.second, 0.0f, 0.0f, mTextureRotation );
594 else if ( facade & 2 )
596 p = rotateCoords( p.first, p.second, 0.0f, 0.0f, mTextureRotation );
598 mData << p.first << p.second;
609 for (
int i = 2; i >= 0; i-- )
613 mData << static_cast<float>( exterior->
xAt( i ) - mOriginX )
614 <<
static_cast<float>( exterior->
yAt( i ) - mOriginY )
615 <<
static_cast<float>( mNoZ ? 0 : exterior->
zAt( i ) );
617 mData << -pNormal.x() << -pNormal.y() << -pNormal.z();
621 mData << static_cast<float>( exterior->
xAt( i ) - mOriginX )
622 <<
static_cast<float>( mNoZ ? 0 : exterior->
zAt( i ) )
623 <<
static_cast<float>( - exterior->
yAt( i ) + mOriginY );
625 mData << -pNormal.x() << -pNormal.z() << pNormal.y();
628 if ( mAddTextureCoords )
630 std::pair<float, float> p( triangle->
xAt( i ), triangle->
yAt( i ) );
633 p = rotateCoords( p.first, p.second, 0.0f, 0.0f, mTextureRotation );
635 else if ( facade & 2 )
637 p = rotateCoords( p.first, p.second, 0.0f, 0.0f, mTextureRotation );
639 mData << p.first << p.second;
644 else if ( mTessellatedFacade & facade )
647 rotatePolygonToXYPlane();
656 if ( polygonSimplified.
isNull() )
658 mError = QObject::tr(
"geometry simplification failed - skipping" );
661 const QgsPolygon *polygonSimplifiedData = qgsgeometry_cast<const QgsPolygon *>( polygonSimplified.
constGet() );
666 mError = QObject::tr(
"geometry's coordinates are too close to each other and simplification failed - skipping" );
671 polygonNew.reset( polygonSimplifiedData->
clone() );
675 QList< std::vector<p2t::Point *> > polylinesToDelete;
676 QHash<p2t::Point *, float> z;
679 std::vector<p2t::Point *> polyline;
680 _ringToPoly2tri( qgsgeometry_cast< const QgsLineString * >( polygonNew->exteriorRing() ), polyline, mNoZ ?
nullptr : &z );
681 polylinesToDelete << polyline;
683 std::unique_ptr<p2t::CDT> cdt(
new p2t::CDT( polyline ) );
686 for (
int i = 0; i < polygonNew->numInteriorRings(); ++i )
688 std::vector<p2t::Point *> holePolyline;
689 const QgsLineString *hole = qgsgeometry_cast< const QgsLineString *>( polygonNew->interiorRing( i ) );
691 _ringToPoly2tri( hole, holePolyline, mNoZ ?
nullptr : &z );
693 cdt->AddHole( holePolyline );
694 polylinesToDelete << holePolyline;
702 std::vector<p2t::Triangle *> triangles = cdt->GetTriangles();
704 mData.reserve( mData.size() + 3 * triangles.size() * (
stride() /
sizeof( float ) ) );
705 for (
size_t i = 0; i < triangles.size(); ++i )
707 p2t::Triangle *t = triangles[i];
708 for (
int j = 0; j < 3; ++j )
710 p2t::Point *p = t->GetPoint( j );
711 QVector4D pt( p->x, p->y, mNoZ ? 0 : z[p], 0 );
713 pt = *toOldBase * pt;
714 const double fx = ( pt.x() / scale ) - mOriginX + pt0.
x();
715 const double fy = ( pt.y() / scale ) - mOriginY + pt0.
y();
716 const double baseHeight = mNoZ ? 0 : ( pt.z() + pt0.
z() );
717 const double fz = mNoZ ? 0 : ( pt.z() + extrusionHeight + pt0.
z() );
718 if ( baseHeight < zMin )
720 if ( baseHeight > zMaxBase )
721 zMaxBase = baseHeight;
722 if ( fz > zMaxExtruded )
727 mData << static_cast<float>( fx ) <<
static_cast<float>( fy ) <<
static_cast<float>( fz );
729 mData << pNormal.x() << pNormal.y() << pNormal.z();
733 mData << static_cast<float>( fx ) <<
static_cast<float>( fz ) <<
static_cast<float>( -fy );
735 mData << pNormal.x() << pNormal.z() << - pNormal.y();
738 if ( mAddTextureCoords )
740 const std::pair<float, float> pr = rotateCoords( p->x, p->y, 0.0f, 0.0f, mTextureRotation );
741 mData << pr.first << pr.second;
748 for (
int j = 2; j >= 0; --j )
750 p2t::Point *p = t->GetPoint( j );
751 QVector4D pt( p->x, p->y, mNoZ ? 0 : z[p], 0 );
753 pt = *toOldBase * pt;
754 const double fx = ( pt.x() / scale ) - mOriginX + pt0.
x();
755 const double fy = ( pt.y() / scale ) - mOriginY + pt0.
y();
756 const double fz = mNoZ ? 0 : ( pt.z() + extrusionHeight + pt0.
z() );
760 mData << static_cast<float>( fx ) <<
static_cast<float>( fy ) <<
static_cast<float>( fz );
762 mData << -pNormal.x() << -pNormal.y() << -pNormal.z();
766 mData << static_cast<float>( fx ) <<
static_cast<float>( fz ) <<
static_cast<float>( -fy );
768 mData << -pNormal.x() << -pNormal.z() << pNormal.y();
771 if ( mAddTextureCoords )
773 const std::pair<float, float> pr = rotateCoords( p->x, p->y, 0.0f, 0.0f, mTextureRotation );
774 mData << pr.first << pr.second;
780 catch ( std::runtime_error &err )
786 mError = QObject::tr(
"An unknown error occurred" );
789 for (
int i = 0; i < polylinesToDelete.count(); ++i )
790 qDeleteAll( polylinesToDelete[i] );
794 if ( extrusionHeight != 0 && ( mTessellatedFacade & 1 ) )
796 _makeWalls( *exterior,
false, extrusionHeight, mData, mAddNormals, mAddTextureCoords, mOriginX, mOriginY, mTextureRotation, mOutputZUp );
799 _makeWalls( *qgsgeometry_cast< const QgsLineString * >( polygon.
interiorRing( i ) ),
true, extrusionHeight, mData, mAddNormals, mAddTextureCoords, mOriginX, mOriginY, mTextureRotation, mOutputZUp );
801 if ( zMaxBase + extrusionHeight > zMaxExtruded )
802 zMaxExtruded = zMaxBase + extrusionHeight;
807 if ( zMaxExtruded > mZMax )
808 mZMax = zMaxExtruded;
809 if ( zMaxBase > mZMax )
815 return mData.size() / (
stride() /
sizeof( float ) );
820 std::unique_ptr< QgsMultiPolygon > mp = std::make_unique< QgsMultiPolygon >();
821 const auto nVals = mData.size();
822 mp->reserve( nVals / 9 );
823 for (
auto i =
decltype( nVals ) {0}; i + 8 < nVals; i += 9 )
827 const QgsPoint p1( mData[i + 0], mData[i + 1], mData[i + 2] );
828 const QgsPoint p2( mData[i + 3], mData[i + 4], mData[i + 5] );
829 const QgsPoint p3( mData[i + 6], mData[i + 7], mData[i + 8] );
835 const QgsPoint p1( mData[i + 0], -mData[i + 2], mData[i + 1] );
836 const QgsPoint p2( mData[i + 3], -mData[i + 5], mData[i + 4] );
837 const QgsPoint p3( mData[i + 6], -mData[i + 8], mData[i + 7] );
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.
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.
bool isEmpty() const override
Returns true if the geometry is empty.
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.
double width() const
Returns the width of the rectangle.
bool isNull() const
Test if the rectangle is null (holding no spatial information).
double height() const
Returns the height of the rectangle.
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