QGIS API Documentation  3.0.2-Girona (307d082)
qgsmemoryprovider.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  memoryprovider.cpp - provider with storage in memory
3  ------------------
4  begin : June 2008
5  copyright : (C) 2008 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 "qgsmemoryprovider.h"
18 
19 #include "qgsfeature.h"
20 #include "qgsfields.h"
21 #include "qgsgeometry.h"
22 #include "qgslogger.h"
23 #include "qgsspatialindex.h"
25 
26 #include <QUrl>
27 #include <QRegExp>
28 
30 
31 static const QString TEXT_PROVIDER_KEY = QStringLiteral( "memory" );
32 static const QString TEXT_PROVIDER_DESCRIPTION = QStringLiteral( "Memory provider" );
33 
34 QgsMemoryProvider::QgsMemoryProvider( const QString &uri )
35  : QgsVectorDataProvider( uri )
36 
37 {
38  // Initialize the geometry with the uri to support old style uri's
39  // (ie, just 'point', 'line', 'polygon')
40  QUrl url = QUrl::fromEncoded( uri.toUtf8() );
41  QString geometry;
42  if ( url.hasQueryItem( QStringLiteral( "geometry" ) ) )
43  {
44  geometry = url.queryItemValue( QStringLiteral( "geometry" ) );
45  }
46  else
47  {
48  geometry = url.path();
49  }
50 
51  if ( geometry.toLower() == QLatin1String( "none" ) )
52  {
53  mWkbType = QgsWkbTypes::NoGeometry;
54  }
55  else
56  {
57  mWkbType = QgsWkbTypes::parseType( geometry );
58  }
59 
60  if ( url.hasQueryItem( QStringLiteral( "crs" ) ) )
61  {
62  QString crsDef = url.queryItemValue( QStringLiteral( "crs" ) );
63  mCrs.createFromString( crsDef );
64  }
65 
66  mNextFeatureId = 1;
67 
68  setNativeTypes( QList< NativeType >()
69  << QgsVectorDataProvider::NativeType( tr( "Whole number (integer)" ), QStringLiteral( "integer" ), QVariant::Int, 0, 10 )
70  // Decimal number from OGR/Shapefile/dbf may come with length up to 32 and
71  // precision up to length-2 = 30 (default, if width is not specified in dbf is length = 24 precision = 15)
72  // We know that double (QVariant::Double) has only 15-16 significant numbers,
73  // but setting that correct limits would disable the use of memory provider with
74  // data from Shapefiles. In any case, the data are handled as doubles.
75  // So the limits set here are not correct but enable use of data from Shapefiles.
76  << QgsVectorDataProvider::NativeType( tr( "Decimal number (real)" ), QStringLiteral( "double" ), QVariant::Double, 0, 32, 0, 30 )
77  << QgsVectorDataProvider::NativeType( tr( "Text (string)" ), QStringLiteral( "string" ), QVariant::String, 0, 255 )
78 
79  // date type
80  << QgsVectorDataProvider::NativeType( tr( "Date" ), QStringLiteral( "date" ), QVariant::Date, -1, -1, -1, -1 )
81  << QgsVectorDataProvider::NativeType( tr( "Time" ), QStringLiteral( "time" ), QVariant::Time, -1, -1, -1, -1 )
82  << QgsVectorDataProvider::NativeType( tr( "Date & Time" ), QStringLiteral( "datetime" ), QVariant::DateTime, -1, -1, -1, -1 )
83 
84  // integer types
85  << QgsVectorDataProvider::NativeType( tr( "Whole number (smallint - 16bit)" ), QStringLiteral( "int2" ), QVariant::Int, -1, -1, 0, 0 )
86  << QgsVectorDataProvider::NativeType( tr( "Whole number (integer - 32bit)" ), QStringLiteral( "int4" ), QVariant::Int, -1, -1, 0, 0 )
87  << QgsVectorDataProvider::NativeType( tr( "Whole number (integer - 64bit)" ), QStringLiteral( "int8" ), QVariant::LongLong, -1, -1, 0, 0 )
88  << QgsVectorDataProvider::NativeType( tr( "Decimal number (numeric)" ), QStringLiteral( "numeric" ), QVariant::Double, 1, 20, 0, 20 )
89  << QgsVectorDataProvider::NativeType( tr( "Decimal number (decimal)" ), QStringLiteral( "decimal" ), QVariant::Double, 1, 20, 0, 20 )
90 
91  // floating point
92  << QgsVectorDataProvider::NativeType( tr( "Decimal number (real)" ), QStringLiteral( "real" ), QVariant::Double, -1, -1, -1, -1 )
93  << QgsVectorDataProvider::NativeType( tr( "Decimal number (double)" ), QStringLiteral( "double precision" ), QVariant::Double, -1, -1, -1, -1 )
94 
95  // string types
96  << QgsVectorDataProvider::NativeType( tr( "Text, unlimited length (text)" ), QStringLiteral( "text" ), QVariant::String, -1, -1, -1, -1 )
97  );
98 
99  if ( url.hasQueryItem( QStringLiteral( "field" ) ) )
100  {
101  QList<QgsField> attributes;
102  QRegExp reFieldDef( "\\:"
103  "(int|integer|long|int8|real|double|string|date|time|datetime)" // type
104  "(?:\\((\\-?\\d+)" // length
105  "(?:\\,(\\d+))?" // precision
106  "\\))?(\\[\\])?" // array
107  "$", Qt::CaseInsensitive );
108  QStringList fields = url.allQueryItemValues( QStringLiteral( "field" ) );
109  for ( int i = 0; i < fields.size(); i++ )
110  {
111  QString name = fields.at( i );
112  QVariant::Type type = QVariant::String;
113  QVariant::Type subType = QVariant::Invalid;
114  QString typeName( QStringLiteral( "string" ) );
115  int length = 255;
116  int precision = 0;
117 
118  int pos = reFieldDef.indexIn( name );
119  if ( pos >= 0 )
120  {
121  name = name.mid( 0, pos );
122  typeName = reFieldDef.cap( 1 ).toLower();
123  if ( typeName == QLatin1String( "int" ) || typeName == QLatin1String( "integer" ) )
124  {
125  type = QVariant::Int;
126  typeName = QStringLiteral( "integer" );
127  length = -1;
128  }
129  else if ( typeName == QLatin1String( "int8" ) || typeName == QLatin1String( "long" ) )
130  {
131  type = QVariant::LongLong;
132  typeName = QStringLiteral( "int8" );
133  length = -1;
134  }
135  else if ( typeName == QLatin1String( "real" ) || typeName == QLatin1String( "double" ) )
136  {
137  type = QVariant::Double;
138  typeName = QStringLiteral( "double" );
139  length = 20;
140  precision = 5;
141  }
142  else if ( typeName == QLatin1String( "date" ) )
143  {
144  type = QVariant::Date;
145  typeName = QStringLiteral( "date" );
146  length = -1;
147  }
148  else if ( typeName == QLatin1String( "time" ) )
149  {
150  type = QVariant::Time;
151  typeName = QStringLiteral( "time" );
152  length = -1;
153  }
154  else if ( typeName == QLatin1String( "datetime" ) )
155  {
156  type = QVariant::DateTime;
157  typeName = QStringLiteral( "datetime" );
158  length = -1;
159  }
160 
161  if ( !reFieldDef.cap( 2 ).isEmpty() )
162  {
163  length = reFieldDef.cap( 2 ).toInt();
164  }
165  if ( !reFieldDef.cap( 3 ).isEmpty() )
166  {
167  precision = reFieldDef.cap( 3 ).toInt();
168  }
169  if ( !reFieldDef.cap( 4 ).isEmpty() )
170  {
171  //array
172  subType = type;
173  type = ( subType == QVariant::String ? QVariant::StringList : QVariant::List );
174  }
175  }
176  if ( !name.isEmpty() )
177  attributes.append( QgsField( name, type, typeName, length, precision, QLatin1String( "" ), subType ) );
178  }
179  addAttributes( attributes );
180  }
181 
182  if ( url.hasQueryItem( QStringLiteral( "index" ) ) && url.queryItemValue( QStringLiteral( "index" ) ) == QLatin1String( "yes" ) )
183  {
184  createSpatialIndex();
185  }
186 
187 }
188 
189 QgsMemoryProvider::~QgsMemoryProvider()
190 {
191  delete mSpatialIndex;
192 }
193 
194 QString QgsMemoryProvider::providerKey()
195 {
196  return TEXT_PROVIDER_KEY;
197 }
198 
199 QString QgsMemoryProvider::providerDescription()
200 {
201  return TEXT_PROVIDER_DESCRIPTION;
202 }
203 
204 QgsMemoryProvider *QgsMemoryProvider::createProvider( const QString &uri )
205 {
206  return new QgsMemoryProvider( uri );
207 }
208 
209 QgsAbstractFeatureSource *QgsMemoryProvider::featureSource() const
210 {
211  return new QgsMemoryFeatureSource( this );
212 }
213 
214 QString QgsMemoryProvider::dataSourceUri( bool expandAuthConfig ) const
215 {
216  Q_UNUSED( expandAuthConfig )
217 
218  QUrl uri( QStringLiteral( "memory" ) );
219  QString geometry = QgsWkbTypes::displayString( mWkbType );
220  uri.addQueryItem( QStringLiteral( "geometry" ), geometry );
221 
222  if ( mCrs.isValid() )
223  {
224  QString crsDef( QLatin1String( "" ) );
225  QString authid = mCrs.authid();
226  if ( authid.startsWith( QLatin1String( "EPSG:" ) ) )
227  {
228  crsDef = authid;
229  }
230  else
231  {
232  int srid = mCrs.postgisSrid();
233  if ( srid )
234  {
235  crsDef = QStringLiteral( "postgis:%1" ).arg( srid );
236  }
237  else
238  {
239  crsDef = QStringLiteral( "wkt:%1" ).arg( mCrs.toWkt() );
240  }
241  }
242  uri.addQueryItem( QStringLiteral( "crs" ), crsDef );
243  }
244  if ( mSpatialIndex )
245  {
246  uri.addQueryItem( QStringLiteral( "index" ), QStringLiteral( "yes" ) );
247  }
248 
249  QgsAttributeList attrs = const_cast<QgsMemoryProvider *>( this )->attributeIndexes();
250  for ( int i = 0; i < attrs.size(); i++ )
251  {
252  QgsField field = mFields.at( attrs[i] );
253  QString fieldDef = field.name();
254  fieldDef.append( QStringLiteral( ":%2(%3,%4)" ).arg( field.typeName() ).arg( field.length() ).arg( field.precision() ) );
255  uri.addQueryItem( QStringLiteral( "field" ), fieldDef );
256  }
257 
258  return QString( uri.toEncoded() );
259 
260 }
261 
262 QString QgsMemoryProvider::storageType() const
263 {
264  return QStringLiteral( "Memory storage" );
265 }
266 
267 QgsFeatureIterator QgsMemoryProvider::getFeatures( const QgsFeatureRequest &request ) const
268 {
269  return QgsFeatureIterator( new QgsMemoryFeatureIterator( new QgsMemoryFeatureSource( this ), true, request ) );
270 }
271 
272 
273 QgsRectangle QgsMemoryProvider::extent() const
274 {
275  if ( mExtent.isEmpty() && !mFeatures.isEmpty() )
276  {
277  mExtent.setMinimal();
278  if ( mSubsetString.isEmpty() )
279  {
280  // fast way - iterate through all features
281  Q_FOREACH ( const QgsFeature &feat, mFeatures )
282  {
283  if ( feat.hasGeometry() )
284  mExtent.combineExtentWith( feat.geometry().boundingBox() );
285  }
286  }
287  else
288  {
289  QgsFeature f;
290  QgsFeatureIterator fi = getFeatures( QgsFeatureRequest().setSubsetOfAttributes( QgsAttributeList() ) );
291  while ( fi.nextFeature( f ) )
292  {
293  if ( f.hasGeometry() )
294  mExtent.combineExtentWith( f.geometry().boundingBox() );
295  }
296  }
297  }
298  else if ( mFeatures.isEmpty() )
299  {
300  mExtent.setMinimal();
301  }
302 
303  return mExtent;
304 }
305 
306 QgsWkbTypes::Type QgsMemoryProvider::wkbType() const
307 {
308  return mWkbType;
309 }
310 
311 long QgsMemoryProvider::featureCount() const
312 {
313  if ( mSubsetString.isEmpty() )
314  return mFeatures.count();
315 
316  // subset string set, no alternative but testing each feature
317  QgsFeatureIterator fit = QgsFeatureIterator( new QgsMemoryFeatureIterator( new QgsMemoryFeatureSource( this ), true, QgsFeatureRequest().setSubsetOfAttributes( QgsAttributeList() ) ) );
318  int count = 0;
319  QgsFeature feature;
320  while ( fit.nextFeature( feature ) )
321  {
322  count++;
323  }
324  return count;
325 }
326 
327 QgsFields QgsMemoryProvider::fields() const
328 {
329  return mFields;
330 }
331 
332 bool QgsMemoryProvider::isValid() const
333 {
334  return ( mWkbType != QgsWkbTypes::Unknown );
335 }
336 
337 QgsCoordinateReferenceSystem QgsMemoryProvider::crs() const
338 {
339  // TODO: make provider projection-aware
340  return mCrs; // return default CRS
341 }
342 
343 
344 bool QgsMemoryProvider::addFeatures( QgsFeatureList &flist, Flags )
345 {
346  bool result = true;
347  // whether or not to update the layer extent on the fly as we add features
348  bool updateExtent = mFeatures.isEmpty() || !mExtent.isEmpty();
349 
350  int fieldCount = mFields.count();
351 
352  // TODO: sanity checks of fields
353  for ( QgsFeatureList::iterator it = flist.begin(); it != flist.end(); ++it )
354  {
355  it->setId( mNextFeatureId );
356  it->setValid( true );
357  if ( it->attributes().count() < fieldCount )
358  {
359  // ensure features have the correct number of attributes by padding
360  // them with null attributes for missing values
361  QgsAttributes attributes = it->attributes();
362  for ( int i = it->attributes().count(); i < mFields.count(); ++i )
363  {
364  attributes.append( QVariant( mFields.at( i ).type() ) );
365  }
366  it->setAttributes( attributes );
367  }
368  else if ( it->attributes().count() > fieldCount )
369  {
370  // too many attributes
371  pushError( tr( "Feature has too many attributes (expecting %1, received %2)" ).arg( fieldCount ).arg( it->attributes().count() ) );
372  QgsAttributes attributes = it->attributes();
373  attributes.resize( mFields.count() );
374  it->setAttributes( attributes );
375  }
376 
377  if ( it->hasGeometry() && mWkbType == QgsWkbTypes::NoGeometry )
378  {
379  it->clearGeometry();
380  }
381  else if ( it->hasGeometry() && QgsWkbTypes::geometryType( it->geometry().wkbType() ) !=
382  QgsWkbTypes::geometryType( mWkbType ) )
383  {
384  pushError( tr( "Could not add feature with geometry type %1 to layer of type %2" ).arg( QgsWkbTypes::displayString( it->geometry().wkbType() ),
385  QgsWkbTypes::displayString( mWkbType ) ) );
386  result = false;
387  continue;
388  }
389 
390  mFeatures.insert( mNextFeatureId, *it );
391 
392  if ( it->hasGeometry() )
393  {
394  if ( updateExtent )
395  mExtent.combineExtentWith( it->geometry().boundingBox() );
396 
397  // update spatial index
398  if ( mSpatialIndex )
399  mSpatialIndex->insertFeature( *it );
400  }
401 
402  mNextFeatureId++;
403  }
404 
405  return result;
406 }
407 
408 bool QgsMemoryProvider::deleteFeatures( const QgsFeatureIds &id )
409 {
410  for ( QgsFeatureIds::const_iterator it = id.begin(); it != id.end(); ++it )
411  {
412  QgsFeatureMap::iterator fit = mFeatures.find( *it );
413 
414  // check whether such feature exists
415  if ( fit == mFeatures.end() )
416  continue;
417 
418  // update spatial index
419  if ( mSpatialIndex )
420  mSpatialIndex->deleteFeature( *fit );
421 
422  mFeatures.erase( fit );
423  }
424 
425  updateExtents();
426 
427  return true;
428 }
429 
430 bool QgsMemoryProvider::addAttributes( const QList<QgsField> &attributes )
431 {
432  for ( QList<QgsField>::const_iterator it = attributes.begin(); it != attributes.end(); ++it )
433  {
434  switch ( it->type() )
435  {
436  case QVariant::Int:
437  case QVariant::Double:
438  case QVariant::String:
439  case QVariant::Date:
440  case QVariant::Time:
441  case QVariant::DateTime:
442  case QVariant::LongLong:
443  case QVariant::StringList:
444  case QVariant::List:
445  break;
446  default:
447  QgsDebugMsg( "Field type not supported: " + it->typeName() );
448  continue;
449  }
450  // add new field as a last one
451  mFields.append( *it );
452 
453  for ( QgsFeatureMap::iterator fit = mFeatures.begin(); fit != mFeatures.end(); ++fit )
454  {
455  QgsFeature &f = fit.value();
456  QgsAttributes attr = f.attributes();
457  attr.append( QVariant() );
458  f.setAttributes( attr );
459  }
460  }
461  return true;
462 }
463 
464 bool QgsMemoryProvider::renameAttributes( const QgsFieldNameMap &renamedAttributes )
465 {
466  QgsFieldNameMap::const_iterator renameIt = renamedAttributes.constBegin();
467  bool result = true;
468  for ( ; renameIt != renamedAttributes.constEnd(); ++renameIt )
469  {
470  int fieldIndex = renameIt.key();
471  if ( fieldIndex < 0 || fieldIndex >= mFields.count() )
472  {
473  result = false;
474  continue;
475  }
476  if ( mFields.indexFromName( renameIt.value() ) >= 0 )
477  {
478  //field name already in use
479  result = false;
480  continue;
481  }
482 
483  mFields[ fieldIndex ].setName( renameIt.value() );
484  }
485  return result;
486 }
487 
488 bool QgsMemoryProvider::deleteAttributes( const QgsAttributeIds &attributes )
489 {
490  QList<int> attrIdx = attributes.toList();
491  std::sort( attrIdx.begin(), attrIdx.end(), std::greater<int>() );
492 
493  // delete attributes one-by-one with decreasing index
494  for ( QList<int>::const_iterator it = attrIdx.constBegin(); it != attrIdx.constEnd(); ++it )
495  {
496  int idx = *it;
497  mFields.remove( idx );
498 
499  for ( QgsFeatureMap::iterator fit = mFeatures.begin(); fit != mFeatures.end(); ++fit )
500  {
501  QgsFeature &f = fit.value();
502  QgsAttributes attr = f.attributes();
503  attr.remove( idx );
504  f.setAttributes( attr );
505  }
506  }
507  return true;
508 }
509 
510 bool QgsMemoryProvider::changeAttributeValues( const QgsChangedAttributesMap &attr_map )
511 {
512  for ( QgsChangedAttributesMap::const_iterator it = attr_map.begin(); it != attr_map.end(); ++it )
513  {
514  QgsFeatureMap::iterator fit = mFeatures.find( it.key() );
515  if ( fit == mFeatures.end() )
516  continue;
517 
518  const QgsAttributeMap &attrs = it.value();
519  for ( QgsAttributeMap::const_iterator it2 = attrs.constBegin(); it2 != attrs.constEnd(); ++it2 )
520  fit->setAttribute( it2.key(), it2.value() );
521  }
522  return true;
523 }
524 
525 bool QgsMemoryProvider::changeGeometryValues( const QgsGeometryMap &geometry_map )
526 {
527  for ( QgsGeometryMap::const_iterator it = geometry_map.begin(); it != geometry_map.end(); ++it )
528  {
529  QgsFeatureMap::iterator fit = mFeatures.find( it.key() );
530  if ( fit == mFeatures.end() )
531  continue;
532 
533  // update spatial index
534  if ( mSpatialIndex )
535  mSpatialIndex->deleteFeature( *fit );
536 
537  fit->setGeometry( it.value() );
538 
539  // update spatial index
540  if ( mSpatialIndex )
541  mSpatialIndex->insertFeature( *fit );
542  }
543 
544  updateExtents();
545 
546  return true;
547 }
548 
549 QString QgsMemoryProvider::subsetString() const
550 {
551  return mSubsetString;
552 }
553 
554 bool QgsMemoryProvider::setSubsetString( const QString &theSQL, bool updateFeatureCount )
555 {
556  Q_UNUSED( updateFeatureCount );
557 
558  if ( !theSQL.isEmpty() )
559  {
560  QgsExpression tempExpression( theSQL );
561  if ( tempExpression.hasParserError() )
562  return false;
563  }
564 
565  mSubsetString = theSQL;
566  clearMinMaxCache();
567  mExtent.setMinimal();
568 
569  emit dataChanged();
570  return true;
571 }
572 
573 bool QgsMemoryProvider::createSpatialIndex()
574 {
575  if ( !mSpatialIndex )
576  {
577  mSpatialIndex = new QgsSpatialIndex();
578 
579  // add existing features to index
580  for ( QgsFeatureMap::const_iterator it = mFeatures.constBegin(); it != mFeatures.constEnd(); ++it )
581  {
582  mSpatialIndex->insertFeature( *it );
583  }
584  }
585  return true;
586 }
587 
588 QgsVectorDataProvider::Capabilities QgsMemoryProvider::capabilities() const
589 {
590  return AddFeatures | DeleteFeatures | ChangeGeometries |
591  ChangeAttributeValues | AddAttributes | DeleteAttributes | RenameAttributes | CreateSpatialIndex |
592  SelectAtId | CircularGeometries;
593 }
594 
595 
596 void QgsMemoryProvider::updateExtents()
597 {
598  mExtent.setMinimal();
599 }
600 
601 QString QgsMemoryProvider::name() const
602 {
603  return TEXT_PROVIDER_KEY;
604 }
605 
606 QString QgsMemoryProvider::description() const
607 {
608  return TEXT_PROVIDER_DESCRIPTION;
609 }
610 
611 
Wrapper for iterator of features from vector data provider or vector layer.
QMap< QgsFeatureId, QgsGeometry > QgsGeometryMap
Definition: qgsfeature.h:537
A rectangle specified with double values.
Definition: qgsrectangle.h:39
void setMinimal()
Set a rectangle so that min corner is at max and max corner is at min.
QString name
Definition: qgsfield.h:57
int precision
Definition: qgsfield.h:54
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeature.h:544
QList< QgsFeature > QgsFeatureList
Definition: qgsfeature.h:549
Container of fields for a vector layer.
Definition: qgsfields.h:42
void setAttributes(const QgsAttributes &attrs)
Sets the feature&#39;s attributes.
Definition: qgsfeature.cpp:127
static Type parseType(const QString &wktStr)
Attempts to extract the WKB type from a WKT string.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:62
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:190
int length
Definition: qgsfield.h:53
QSet< int > QgsAttributeIds
Type
The WKB type describes the number of dimensions a geometry has.
Definition: qgswkbtypes.h:67
QString typeName() const
Gets the field type.
Definition: qgsfield.cpp:103
QMap< int, QVariant > QgsAttributeMap
Definition: qgsattributes.h:39
static GeometryType geometryType(Type type)
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
Definition: qgswkbtypes.h:663
This class wraps a request for features to a vector layer (or directly its vector data provider)...
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:48
QgsGeometry geometry() const
Returns the geometry associated with this feature.
Definition: qgsfeature.cpp:101
Base class that can be used for any class that is capable of returning features.
QMap< int, QString > QgsFieldNameMap
Definition: qgsattributes.h:45
A spatial index for QgsFeature objects.
QMap< QgsFeatureId, QgsAttributeMap > QgsChangedAttributesMap
Definition: qgsfeature.h:528
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
This class represents a coordinate reference system (CRS).
static QString displayString(Type type)
Returns a display string type for a WKB type, e.g., the geometry name used in WKT geometry representa...
QList< int > QgsAttributeList
Definition: qgsfield.h:27
bool nextFeature(QgsFeature &f)
This is the base class for vector data providers.
A vector of attributes.
Definition: qgsattributes.h:58
QgsAttributes attributes
Definition: qgsfeature.h:72