QGIS图层数据接口类源码解析

1.QGIS图层数据接口类

常用的GIS数据类型是矢量、栅格,QGIS也提供了网格(mesh)数据模型mdalprovider类。矢量数据格式较为丰富,常见的如esri的shape file,CAD的dxf等都是属于ogr格式,所以新版本ogrdataprovider类内置到qgis_core中,不再作为单独的ogrprovider.dll插件。此外还有空间数据库格式的矢量图层,不同的数据库分别用相应的插件库进行实现,如spatial sqlite数据库的spatialiteprovider.dll插件库,postgres gis数据库图层的postgresprovider.dll插件。这里详细解析spatial sqlite矢量数据库图层类的源码,其他类型的编码思想基本类似。

<> QgsDataProvider <> QgsVectorDataProvider <> QgsFeatureSink <> QgsFeatureSource QgsSpatiaLiteProvider friend QgsSpatiaLiteFeatureSource … void closeDb bool checkLayerType bool getGeometryDetails bool getTableGeometryDetails bool prepareStatement bool getFeature void updatePrimaryKeyCapabilities QString createIndexName QgsAbstractFeatureSource featureSource() QgsPostgresProvider QgsOgrProvider <> QgsRasterDataProvider <> QgsRasterInterface QgsMapLayer QgsVectorLayer QgsVectorDataProvider mDataProvider; QgsDataProvider *dataProvider()

2.QgsSpatiaLiteProvider

创建spatialite矢量图层对象的代码是:

QgsDataSourceUri uri;
uri.setDatabase(path);
uri.setTable(tableName);
uri.setGeometryColumn("geom");
uri.setKeyColumn("pk");
QgsVectorLayer* layer = new QgsVectorLayer(uri.uri(), tableName, "spatialite");

如果有必要可以继续对该图层创建空间索引,这段代码展示了图层创建索引的过程,也为了说明图层对象的操作最终由插件库执行的实现逻辑。


if (layer->hasSpatialIndex() != QgsFeatureSource::SpatialIndexPresence::SpatialIndexPresent)
{
        const QString providerName{ layer->dataProvider()->name() };
        QgsProviderMetadata *providerMetadata{ QgsProviderRegistry::instance()->providerMetadata(providerName) };
        if (providerMetadata)
        {

            std::unique_ptr< QgsAbstractDatabaseProviderConnection > conn{ static_cast<QgsAbstractDatabaseProviderConnection *>(providerMetadata->createConnection(layer->dataProvider()->uri().uri(),{})) };
            if (conn)
            {
                QString tableSchema;
                QString tableName;
                const QVariantMap sourceParts = providerMetadata->decodeUri(layer->source());
                tableName = sourceParts.value(QStringLiteral("layerName")).toString();

                if (tableName.isEmpty())
                {
                    tableName = sourceParts.value(QStringLiteral("table")).toString();
                    tableSchema = sourceParts.value(QStringLiteral("schema")).toString();
                }
                conn->createSpatialIndex(tableSchema, tableName);
            }
    }
}

当然,在spatial sqlite中建立空间索引又引用了spatialsqlite和sqlite3这两个开源库,这些开源库又在qgis的插件库中做了一层封装。关于这两个开源库的用法不再详述。

通过图层对象可以获取其对应的dataprovider指针对象,根据不同的图层类型,运行时确定具体的dataprovider类型。图层对象与数据库的交互操作本质上都将由dataprovider指针对象来执行。那么,可以判定dataprovider对象是在QgsVectorLayer的构造函数中创建的。实际上,源码中在构造函数内调用了setDataSource()函数,setDataSource再调用setDataProvider()函数,从而构造了dataprovider对象,如下:

bool QgsVectorLayer::setDataProvider( QString const &provider,..)
    {
        mDataProvider = qobject_cast<QgsVectorDataProvider *>(
            QgsProviderRegistry::instance()->createProvider( provider, mDataSource, options,flags
        ));
    }

3.getFeatures()

以读取矢量图层要素的源码为例,说明QGIS的编程思想。

QgsFeatureIterator QgsVectorLayer::getFeatures( const QgsFeatureRequest &request ) const
{
  if ( !isValid() || !mDataProvider )
    return QgsFeatureIterator();
  return QgsFeatureIterator( new QgsVectorLayerFeatureIterator( new QgsVectorLayerFeatureSource( this ), true, request ) );
}

在QgsVectorLayerFeatureSource类中,获取到dataprovider对象的数据源:

QgsVectorLayerFeatureSource::QgsVectorLayerFeatureSource( const QgsVectorLayer *layer )
{
  QMutexLocker locker( &layer->mFeatureSourceConstructorMutex );

  mProviderFeatureSource.reset( layer->dataProvider()->featureSource() );
  mFields = layer->fields();
  mId = layer->id();
  ...

 }

最终,实际读取要素的代码是:

bool QgsSpatiaLiteFeatureIterator::prepareStatement( const QString &whereClause, long limit, const QString &orderBy )
{
  if ( !mSqliteHandle )
    return false;
  try
  {
    QString sql = QStringLiteral( "SELECT %1" ).arg( mHasPrimaryKey ? quotedPrimaryKey() : QStringLiteral( "0" ) );
    int colIdx = 1;
        ...

    if ( sqlite3_prepare_v2( mSqliteHandle, sql.toUtf8().constData(), -1, &sqliteStatement, nullptr ) != SQLITE_OK )
    {

      QgsMessageLog::logMessage( QObject::tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( mSqliteHandle ) ), QObject::tr( "SpatiaLite" ) );
      return false;
    }
  }
  catch ( QgsSpatiaLiteProvider::SLFieldNotFound )
  {
    rewind();
    return false;
  }
  return true;
}

bool QgsSpatiaLiteFeatureIterator::getFeature( sqlite3_stmt *stmt, QgsFeature &feature )
{
  bool subsetAttributes = mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes;
  int ret = sqlite3_step( stmt );
  if ( ret == SQLITE_DONE )
  {

    return false;
  }
  if ( ret != SQLITE_ROW )
  {

    QgsMessageLog::logMessage( QObject::tr( "SQLite error getting feature: %1" ).arg( QString::fromUtf8( sqlite3_errmsg( mSqliteHandle ) ) ), QObject::tr( "SpatiaLite" ) );
    return false;
  }

  if ( !mFetchGeometry )
  {

    feature.clearGeometry();
  }
  feature.initAttributes( mSource->mFields.count() );
  feature.setFields( mSource->mFields );
  int ic;
  int n_columns = sqlite3_column_count( stmt );
  for ( ic = 0; ic < n_columns; ic++ )
  {
    if ( ic == 0 )
    {
      if ( mHasPrimaryKey && sqlite3_column_type( stmt, ic ) == SQLITE_INTEGER )
      {

        QgsFeatureId fid = sqlite3_column_int64( stmt, ic );
        QgsDebugMsgLevel( QStringLiteral( "fid=%1" ).arg( fid ), 3 );
        feature.setId( fid );
      }
      else
      {
        QgsDebugMsgLevel( QStringLiteral( "Primary key is not an integer field: setting autoincrement fid" ), 3 );

        mRowNumber++;
        feature.setId( mRowNumber );
      }
    }
    else if ( mFetchGeometry && ic == mGeomColIdx )
    {
      getFeatureGeometry( stmt, ic, feature );
    }
    else
    {
      if ( subsetAttributes )
      {
        if ( ic  mRequest.subsetOfAttributes().size() )
        {
          const int attrIndex = mRequest.subsetOfAttributes().at( ic - 1 );
          const QgsField field = mSource->mFields.at( attrIndex );
          feature.setAttribute( attrIndex, getFeatureAttribute( stmt, ic, field.type(), field.subType() ) );
        }
      }
      else
      {
        const int attrIndex = ic - 1;
        const QgsField field = mSource->mFields.at( attrIndex );
        feature.setAttribute( attrIndex, getFeatureAttribute( stmt, ic, field.type(), field.subType() ) );
      }
    }
  }
  return true;
}

对图层的操作,本质上是对数据库进行操作,无外乎增删改查这些,当然空间数据库有其特有的性质。阅读源码过程中,按照和读取要素同样的道理,就很容易其他编码逻辑了,再去理解、调试、维护源码就容易得多了。

Original: https://blog.csdn.net/wenjunkai19881029/article/details/120563321
Author: kingkaa
Title: QGIS图层数据接口类源码解析

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/816324/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球