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矢量数据库图层类的源码,其他类型的编码思想基本类似。

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

2.QgsSpatiaLiteProvider

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

QgsDataSourceUri uri;
uri.setDatabase(path);//path是sqlite数据库文件的路径
uri.setTable(tableName);//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)
		{
			// Retrieve the DB connection (if any)
			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();
				// This works for PG and spatialite
				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 );
  //将读取图层的要素,转换为从图层的dataprovider中读取要素
  //那么,将由QgsSpatiaLiteFeatureSource类来实现具体的读取功能
  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; // column 0 is primary key
		...
		//省略掉组织sql语句的代码
    if ( sqlite3_prepare_v2( mSqliteHandle, sql.toUtf8().constData(), -1, &sqliteStatement, nullptr ) != SQLITE_OK )
    {
      // some error occurred
      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 )
  {
    // there are no more rows to fetch
    return false;
  }
  if ( ret != SQLITE_ROW )
  {
    // some unexpected error occurred
    QgsMessageLog::logMessage( QObject::tr( "SQLite error getting feature: %1" ).arg( QString::fromUtf8( sqlite3_errmsg( mSqliteHandle ) ) ), QObject::tr( "SpatiaLite" ) );
    return false;
  }
  // one valid row has been fetched from the result set
  if ( !mFetchGeometry )
  {
    // no geometry was required
    feature.clearGeometry();
  }
  feature.initAttributes( mSource->mFields.count() );
  feature.setFields( mSource->mFields ); // allow name-based attribute lookups
  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 )
      {
        // first column always contains the ROWID (or the primary key)
        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 );
        // autoincrement a row number
        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;
}

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

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值