QGIS API Documentation  3.24.2-Tisler (13c1a02865)
qgsline3dsymbol_p.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsline3dsymbol_p.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 "qgsline3dsymbol_p.h"
17 
18 #include "qgsline3dsymbol.h"
19 #include "qgslinematerial_p.h"
20 #include "qgslinevertexdata_p.h"
22 #include "qgstessellator.h"
23 #include "qgs3dmapsettings.h"
24 //#include "qgsterraingenerator.h"
25 #include "qgs3dutils.h"
26 
27 #include "qgsvectorlayer.h"
28 #include "qgsmultilinestring.h"
29 #include "qgsmultipolygon.h"
30 #include "qgsgeos.h"
32 
34 
35 #include <Qt3DExtras/QPhongMaterial>
36 #include <Qt3DRender/QAttribute>
37 #include <Qt3DRender/QBuffer>
38 #include <Qt3DRender/QGeometryRenderer>
39 
41 
42 // -----------
43 
44 
45 class QgsBufferedLine3DSymbolHandler : public QgsFeature3DHandler
46 {
47  public:
48  QgsBufferedLine3DSymbolHandler( const QgsLine3DSymbol *symbol, const QgsFeatureIds &selectedIds )
49  : mSymbol( static_cast< QgsLine3DSymbol *>( symbol->clone() ) )
50  , mSelectedIds( selectedIds ) {}
51 
52  bool prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames ) override;
53  void processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context ) override;
54  void finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context ) override;
55 
56  private:
57 
59  struct LineData
60  {
61  std::unique_ptr<QgsTessellator> tessellator;
62  QVector<QgsFeatureId> triangleIndexFids;
63  QVector<uint> triangleIndexStartingIndices;
64  };
65 
66  void processPolygon( QgsPolygon *polyBuffered, QgsFeatureId fid, float height, float extrusionHeight, const Qgs3DRenderContext &context, LineData &out );
67 
68  void makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, LineData &out, bool selected );
69 
70  // input specific for this class
71  std::unique_ptr< QgsLine3DSymbol > mSymbol;
72  // inputs - generic
73  QgsFeatureIds mSelectedIds;
74 
75  // outputs
76  LineData outNormal;
77  LineData outSelected;
78 };
79 
80 
81 
82 bool QgsBufferedLine3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames )
83 {
84  Q_UNUSED( attributeNames )
85 
86  const QgsPhongTexturedMaterialSettings *texturedMaterialSettings = dynamic_cast< const QgsPhongTexturedMaterialSettings * >( mSymbol->material() );
87 
88  outNormal.tessellator.reset( new QgsTessellator( context.map().origin().x(), context.map().origin().y(), true,
89  false, false, false, texturedMaterialSettings ? texturedMaterialSettings->requiresTextureCoordinates() : false,
90  3,
91  texturedMaterialSettings ? texturedMaterialSettings->textureRotation() : 0 ) );
92  outSelected.tessellator.reset( new QgsTessellator( context.map().origin().x(), context.map().origin().y(), true,
93  false, false, false, texturedMaterialSettings ? texturedMaterialSettings->requiresTextureCoordinates() : false,
94  3,
95  texturedMaterialSettings ? texturedMaterialSettings->textureRotation() : 0 ) );
96 
97  return true;
98 }
99 
100 void QgsBufferedLine3DSymbolHandler::processFeature( const QgsFeature &f, const Qgs3DRenderContext &context )
101 {
102  if ( f.geometry().isNull() )
103  return;
104 
105  LineData &out = mSelectedIds.contains( f.id() ) ? outSelected : outNormal;
106 
107  QgsGeometry geom = f.geometry();
108  const QgsAbstractGeometry *g = geom.constGet()->simplifiedTypeRef();
109 
110  // segmentize curved geometries if necessary
111  if ( QgsWkbTypes::isCurvedType( g->wkbType() ) )
112  {
113  geom = QgsGeometry( g->segmentize() );
114  g = geom.constGet()->simplifiedTypeRef();
115  }
116 
117  // TODO: configurable
118  const int nSegments = 4;
119  const Qgis::EndCapStyle endCapStyle = Qgis::EndCapStyle::Round;
120  const Qgis::JoinStyle joinStyle = Qgis::JoinStyle::Round;
121  const double mitreLimit = 0;
122 
123  const QgsGeos engine( g );
124 
125  double width = mSymbol->width();
126  if ( qgsDoubleNear( width, 0 ) )
127  {
128  // a zero-width buffered line should be treated like a "wall" or "fence" -- we fake this by bumping the width to a very tiny amount,
129  // so that we get a very narrow polygon shape to work with...
130  width = 0.001;
131  }
132 
133  QgsAbstractGeometry *buffered = engine.buffer( width / 2., nSegments, endCapStyle, joinStyle, mitreLimit ); // factory
134  if ( !buffered )
135  return;
136 
137  if ( QgsWkbTypes::flatType( buffered->wkbType() ) == QgsWkbTypes::Polygon )
138  {
139  QgsPolygon *polyBuffered = static_cast<QgsPolygon *>( buffered );
140  processPolygon( polyBuffered, f.id(), mSymbol->height(), mSymbol->extrusionHeight(), context, out );
141  }
142  else if ( QgsWkbTypes::flatType( buffered->wkbType() ) == QgsWkbTypes::MultiPolygon )
143  {
144  QgsMultiPolygon *mpolyBuffered = static_cast<QgsMultiPolygon *>( buffered );
145  for ( int i = 0; i < mpolyBuffered->numGeometries(); ++i )
146  {
147  QgsPolygon *polyBuffered = static_cast<QgsPolygon *>( mpolyBuffered->polygonN( i ) )->clone(); // need to clone individual geometry parts
148  processPolygon( polyBuffered, f.id(), mSymbol->height(), mSymbol->extrusionHeight(), context, out );
149  }
150  delete buffered;
151  }
152 }
153 
154 void QgsBufferedLine3DSymbolHandler::processPolygon( QgsPolygon *polyBuffered, QgsFeatureId fid, float height, float extrusionHeight, const Qgs3DRenderContext &context, LineData &out )
155 {
156  Qgs3DUtils::clampAltitudes( polyBuffered, mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), height, context.map() );
157 
158  Q_ASSERT( out.tessellator->dataVerticesCount() % 3 == 0 );
159  const uint startingTriangleIndex = static_cast<uint>( out.tessellator->dataVerticesCount() / 3 );
160  out.triangleIndexStartingIndices.append( startingTriangleIndex );
161  out.triangleIndexFids.append( fid );
162  out.tessellator->addPolygon( *polyBuffered, extrusionHeight );
163  delete polyBuffered;
164 }
165 
166 void QgsBufferedLine3DSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context )
167 {
168  // create entity for selected and not selected
169  makeEntity( parent, context, outNormal, false );
170  makeEntity( parent, context, outSelected, true );
171 
172  mZMin = std::min( outNormal.tessellator->zMinimum(), outSelected.tessellator->zMinimum() );
173  mZMax = std::max( outNormal.tessellator->zMaximum(), outSelected.tessellator->zMaximum() );
174 }
175 
176 
177 void QgsBufferedLine3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, LineData &out, bool selected )
178 {
179  if ( out.tessellator->dataVerticesCount() == 0 )
180  return; // nothing to show - no need to create the entity
181 
182  QgsMaterialContext materialContext;
183  materialContext.setIsSelected( selected );
184  materialContext.setSelectionColor( context.map().selectionColor() );
185  Qt3DRender::QMaterial *mat = mSymbol->material()->toMaterial( QgsMaterialSettingsRenderingTechnique::Triangles, materialContext );
186 
187  // extract vertex buffer data from tessellator
188  const QByteArray data( ( const char * )out.tessellator->data().constData(), out.tessellator->data().count() * sizeof( float ) );
189  const int nVerts = data.count() / out.tessellator->stride();
190 
191  const QgsPhongTexturedMaterialSettings *texturedMaterialSettings = dynamic_cast< const QgsPhongTexturedMaterialSettings * >( mSymbol->material() );
192 
193  QgsTessellatedPolygonGeometry *geometry = new QgsTessellatedPolygonGeometry( true, false, false,
194  texturedMaterialSettings ? texturedMaterialSettings->requiresTextureCoordinates() : false );
195  geometry->setData( data, nVerts, out.triangleIndexFids, out.triangleIndexStartingIndices );
196 
197  Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
198  renderer->setGeometry( geometry );
199 
200  // make entity
201  Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
202  entity->addComponent( renderer );
203  entity->addComponent( mat );
204  entity->setParent( parent );
205 
206  if ( !selected )
207  entity->findChild<Qt3DRender::QGeometryRenderer *>()->setObjectName( QStringLiteral( "main" ) ); // temporary measure to distinguish between "selected" and "main"
208 
209  // cppcheck wrongly believes entity will leak
210  // cppcheck-suppress memleak
211 }
212 
213 
214 // --------------
215 
216 
217 class QgsSimpleLine3DSymbolHandler : public QgsFeature3DHandler
218 {
219  public:
220  QgsSimpleLine3DSymbolHandler( const QgsLine3DSymbol *symbol, const QgsFeatureIds &selectedIds )
221  : mSymbol( static_cast< QgsLine3DSymbol *>( symbol->clone() ) )
222  , mSelectedIds( selectedIds )
223  {
224  }
225 
226  bool prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames ) override;
227  void processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context ) override;
228  void finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context ) override;
229 
230  private:
231 
232  void makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, QgsLineVertexData &out, bool selected );
233  Qt3DExtras::QPhongMaterial *material( const QgsLine3DSymbol &symbol ) const;
234 
235  // input specific for this class
236  std::unique_ptr< QgsLine3DSymbol > mSymbol;
237  // inputs - generic
238  QgsFeatureIds mSelectedIds;
239 
240  // outputs
241  QgsLineVertexData outNormal;
242  QgsLineVertexData outSelected;
243 };
244 
245 
246 
247 bool QgsSimpleLine3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames )
248 {
249  Q_UNUSED( attributeNames )
250 
251  outNormal.init( mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), mSymbol->height(), &context.map() );
252  outSelected.init( mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), mSymbol->height(), &context.map() );
253 
254  return true;
255 }
256 
257 void QgsSimpleLine3DSymbolHandler::processFeature( const QgsFeature &f, const Qgs3DRenderContext &context )
258 {
259  Q_UNUSED( context )
260  if ( f.geometry().isNull() )
261  return;
262 
263  QgsLineVertexData &out = mSelectedIds.contains( f.id() ) ? outSelected : outNormal;
264 
265  const QgsGeometry geom = f.geometry();
266  const QgsAbstractGeometry *g = geom.constGet();
267  if ( const QgsLineString *ls = qgsgeometry_cast<const QgsLineString *>( g ) )
268  {
269  out.addLineString( *ls );
270  }
271  else if ( const QgsMultiLineString *mls = qgsgeometry_cast<const QgsMultiLineString *>( g ) )
272  {
273  for ( int nGeom = 0; nGeom < mls->numGeometries(); ++nGeom )
274  {
275  const QgsLineString *ls = mls->lineStringN( nGeom );
276  out.addLineString( *ls );
277  }
278  }
279 }
280 
281 void QgsSimpleLine3DSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context )
282 {
283  // create entity for selected and not selected
284  makeEntity( parent, context, outNormal, false );
285  makeEntity( parent, context, outSelected, true );
286 
287  updateZRangeFromPositions( outNormal.vertices );
288  updateZRangeFromPositions( outSelected.vertices );
289 }
290 
291 
292 void QgsSimpleLine3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, QgsLineVertexData &out, bool selected )
293 {
294  if ( out.indexes.isEmpty() )
295  return;
296 
297  // material (only ambient color is used for the color)
298 
299  QgsMaterialContext materialContext;
300  materialContext.setIsSelected( selected );
301  materialContext.setSelectionColor( context.map().selectionColor() );
302  Qt3DRender::QMaterial *mat = mSymbol->material()->toMaterial( QgsMaterialSettingsRenderingTechnique::Lines, materialContext );
303 
304  // geometry renderer
305 
306  Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
307 
308  Qt3DRender::QGeometry *geom = out.createGeometry( entity );
309 
310  Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
311  renderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::LineStrip );
312  renderer->setGeometry( geom );
313  renderer->setVertexCount( out.indexes.count() );
314  renderer->setPrimitiveRestartEnabled( true );
315  renderer->setRestartIndexValue( 0 );
316 
317  // make entity
318  entity->addComponent( renderer );
319  entity->addComponent( mat );
320  entity->setParent( parent );
321 }
322 
323 
324 
325 // --------------
326 
327 
328 class QgsThickLine3DSymbolHandler : public QgsFeature3DHandler
329 {
330  public:
331  QgsThickLine3DSymbolHandler( const QgsLine3DSymbol *symbol, const QgsFeatureIds &selectedIds )
332  : mSymbol( static_cast< QgsLine3DSymbol * >( symbol->clone() ) )
333  , mSelectedIds( selectedIds )
334  {
335  }
336 
337  bool prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames ) override;
338  void processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context ) override;
339  void finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context ) override;
340 
341  private:
342 
343 
344  void makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, QgsLineVertexData &out, bool selected );
345  Qt3DExtras::QPhongMaterial *material( const QgsLine3DSymbol &symbol ) const;
346 
347  // input specific for this class
348  std::unique_ptr< QgsLine3DSymbol > mSymbol;
349  // inputs - generic
350  QgsFeatureIds mSelectedIds;
351 
352  // outputs
353  QgsLineVertexData outNormal;
354  QgsLineVertexData outSelected;
355 };
356 
357 
358 
359 bool QgsThickLine3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames )
360 {
361  Q_UNUSED( attributeNames )
362 
363  outNormal.withAdjacency = true;
364  outSelected.withAdjacency = true;
365  outNormal.init( mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), mSymbol->height(), &context.map() );
366  outSelected.init( mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), mSymbol->height(), &context.map() );
367 
368  return true;
369 }
370 
371 void QgsThickLine3DSymbolHandler::processFeature( const QgsFeature &f, const Qgs3DRenderContext &context )
372 {
373  Q_UNUSED( context )
374  if ( f.geometry().isNull() )
375  return;
376 
377  QgsLineVertexData &out = mSelectedIds.contains( f.id() ) ? outSelected : outNormal;
378 
379  QgsGeometry geom = f.geometry();
380  const QgsAbstractGeometry *g = geom.constGet()->simplifiedTypeRef();
381 
382  // segmentize curved geometries if necessary
383  if ( QgsWkbTypes::isCurvedType( g->wkbType() ) )
384  {
385  geom = QgsGeometry( g->segmentize() );
386  g = geom.constGet()->simplifiedTypeRef();
387  }
388 
389  if ( const QgsLineString *ls = qgsgeometry_cast<const QgsLineString *>( g ) )
390  {
391  out.addLineString( *ls );
392  }
393  else if ( const QgsMultiLineString *mls = qgsgeometry_cast<const QgsMultiLineString *>( g ) )
394  {
395  for ( int nGeom = 0; nGeom < mls->numGeometries(); ++nGeom )
396  {
397  const QgsLineString *ls = mls->lineStringN( nGeom );
398  out.addLineString( *ls );
399  }
400  }
401 }
402 
403 void QgsThickLine3DSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context )
404 {
405  // create entity for selected and not selected
406  makeEntity( parent, context, outNormal, false );
407  makeEntity( parent, context, outSelected, true );
408 
409  updateZRangeFromPositions( outNormal.vertices );
410  updateZRangeFromPositions( outSelected.vertices );
411 }
412 
413 
414 void QgsThickLine3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, QgsLineVertexData &out, bool selected )
415 {
416  if ( out.indexes.isEmpty() )
417  return;
418 
419  // material (only ambient color is used for the color)
420  QgsMaterialContext materialContext;
421  materialContext.setIsSelected( selected );
422  materialContext.setSelectionColor( context.map().selectionColor() );
423  Qt3DRender::QMaterial *mat = mSymbol->material()->toMaterial( QgsMaterialSettingsRenderingTechnique::Lines, materialContext );
424  if ( !mat )
425  {
426  const QgsSimpleLineMaterialSettings defaultMaterial;
427  mat = defaultMaterial.toMaterial( QgsMaterialSettingsRenderingTechnique::Lines, materialContext );
428  }
429 
430  if ( QgsLineMaterial *lineMaterial = dynamic_cast< QgsLineMaterial * >( mat ) )
431  lineMaterial->setLineWidth( mSymbol->width() );
432 
433  Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
434 
435  // geometry renderer
436  Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
437  renderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::LineStripAdjacency );
438  renderer->setGeometry( out.createGeometry( entity ) );
439  renderer->setVertexCount( out.indexes.count() );
440  renderer->setPrimitiveRestartEnabled( true );
441  renderer->setRestartIndexValue( 0 );
442 
443  // make entity
444  entity->addComponent( renderer );
445  entity->addComponent( mat );
446  entity->setParent( parent );
447 }
448 
449 
450 // --------------
451 
452 
453 namespace Qgs3DSymbolImpl
454 {
455 
456  QgsFeature3DHandler *handlerForLine3DSymbol( QgsVectorLayer *layer, const QgsAbstract3DSymbol *symbol )
457  {
458  const QgsLine3DSymbol *lineSymbol = dynamic_cast< const QgsLine3DSymbol * >( symbol );
459  if ( !lineSymbol )
460  return nullptr;
461 
462  if ( lineSymbol->renderAsSimpleLines() )
463  return new QgsThickLine3DSymbolHandler( lineSymbol, layer->selectedFeatureIds() );
464  //return new QgsSimpleLine3DSymbolHandler( symbol, layer->selectedFeatureIds() );
465  else
466  return new QgsBufferedLine3DSymbolHandler( lineSymbol, layer->selectedFeatureIds() );
467  }
468 
469  Qt3DCore::QEntity *entityForLine3DSymbol( const Qgs3DMapSettings &map, QgsVectorLayer *layer, const QgsLine3DSymbol &symbol )
470  {
471  QgsFeature3DHandler *handler = handlerForLine3DSymbol( layer, &symbol );
472  Qt3DCore::QEntity *e = entityFromHandler( handler, map, layer );
473  delete handler;
474  return e;
475  }
476 }
477 
JoinStyle
Join styles for buffers.
Definition: qgis.h:758
EndCapStyle
End cap styles for buffers.
Definition: qgis.h:745
static void clampAltitudes(QgsLineString *lineString, Qgs3DTypes::AltitudeClamping altClamp, Qgs3DTypes::AltitudeBinding altBind, const QgsPoint &centroid, float height, const Qgs3DMapSettings &map)
Clamps altitude of vertices of a linestring according to the settings.
Definition: qgs3dutils.cpp:316
Abstract base class for all geometries.
virtual QgsAbstractGeometry * segmentize(double tolerance=M_PI/180., SegmentationToleranceType toleranceType=MaximumAngle) const
Returns a version of the geometry without curves.
virtual const QgsAbstractGeometry * simplifiedTypeRef() const SIP_HOLDGIL
Returns a reference to the simplest lossless representation of this geometry, e.g.
QgsWkbTypes::Type wkbType() const SIP_HOLDGIL
Returns the WKB type of the geometry.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
QgsGeometry geometry
Definition: qgsfeature.h:67
Q_GADGET QgsFeatureId id
Definition: qgsfeature.h:64
int numGeometries() const SIP_HOLDGIL
Returns the number of geometries within the collection.
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.
Q_GADGET bool isNull
Definition: qgsgeometry.h:127
Does vector analysis using the geos library and handles import, export, exception handling*.
Definition: qgsgeos.h:104
bool renderAsSimpleLines() const
Returns whether the renderer will render data with simple lines (otherwise it uses buffer)
Line string geometry type, with support for z-dimension and m-values.
Definition: qgslinestring.h:44
void setIsSelected(bool isSelected)
Sets whether the material should represent a selected state.
void setSelectionColor(const QColor &color)
Sets the color for representing materials in a selected state.
Multi line string geometry collection.
Multi polygon geometry collection.
QgsPolygon * polygonN(int index)
Returns the polygon with the specified index.
bool requiresTextureCoordinates() const
Returns true if the material requires texture coordinates to be generated during triangulation....
float textureRotation() const
Returns the texture rotation, in degrees.
Polygon geometry type.
Definition: qgspolygon.h:34
Qt3DRender::QMaterial * toMaterial(QgsMaterialSettingsRenderingTechnique technique, const QgsMaterialContext &context) const override
Creates a new QMaterial object representing the material settings.
void setData(const QByteArray &vertexBufferData, int vertexCount, const QVector< QgsFeatureId > &triangleIndexFids, const QVector< uint > &triangleIndexStartingIndices)
Initializes vertex buffer (and other members) from data that were already tessellated.
Class that takes care of tessellation of polygons into triangles.
Represents a vector layer which manages a vector based data sets.
Q_INVOKABLE const QgsFeatureIds & selectedFeatureIds() const
Returns a list of the selected features IDs in this layer.
static bool isCurvedType(Type type) SIP_HOLDGIL
Returns true if the WKB type is a curved type or can contain curved geometries.
Definition: qgswkbtypes.h:911
static Type flatType(Type type) SIP_HOLDGIL
Returns the flat type for a WKB type.
Definition: qgswkbtypes.h:732
@ Triangles
Triangle based rendering (default)
@ Lines
Line based rendering, requires line data.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:1578
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:37
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
Definition: qgsfeatureid.h:28