QgsPostgresProvider源码分析(4)之QgsPostgresFeatureIterator

4.QgsPostgresFeatureIterator类

class QgsPostgresFeatureIterator final: public QgsAbstractFeatureIteratorFromSource<QgsPostgresFeatureSource>
{
};

该类需要对rewind()、close()、fetchFeature()三个关键函数进行重写。
部分成员变量:

  • QgsPostgresConn *mConn = nullptr;
    数据库连接。
  • QString mCursorName;
    游标名,通过该游标查询features。
  • QQueue<QgsFeature> mFeatureQueue;
    保存从数据库提取出的features。
  • int mFetched = 0;
    已经检索的feature数量。

(1) QgsPostgresFeatureIterator()

QgsPostgresFeatureIterator::QgsPostgresFeatureIterator( QgsPostgresFeatureSource *source, bool ownSource, const QgsFeatureRequest &request )
  : QgsAbstractFeatureIteratorFromSource<QgsPostgresFeatureSource>( source, ownSource, request )
{
}

(2) rewind() override

bool QgsPostgresFeatureIterator::rewind()
{
  if ( mClosed )
    return false;

  // move cursor to first record

  mConn->PQexecNR( QStringLiteral( "move absolute 0 in %1" ).arg( mCursorName ) );
  mFeatureQueue.clear();
  mFetched = 0;
  mLastFetch = false;

  return true;
}

rewind()重置检索,将要素的迭代返回到初始状态。

(3) closed() override

bool QgsPostgresFeatureIterator::close()
{
  if ( !mConn )
    return false;

  mConn->closeCursor( mCursorName );

  if ( !mIsTransactionConnection )
  {
    QgsPostgresConnPool::instance()->releaseConnection( mConn );
  }
  mConn = nullptr;

  while ( !mFeatureQueue.empty() )
  {
    mFeatureQueue.dequeue();
  }

  iteratorClosed();

  mClosed = true;
  return true;
}

关闭该迭代器,关闭了数据库连接的游标,并将要素队列的数据全部出队。

(4) fetchFeature() override

bool QgsPostgresFeatureIterator::fetchFeature( QgsFeature &feature )
{
  ......
  while ( true )
  {
    // 1)
    if ( mFeatureQueue.empty() && !mLastFetch )
    {
      QString fetch = QStringLiteral( "FETCH FORWARD %1 FROM %2" ).arg( mFeatureQueueSize ).arg( mCursorName );
      mConn->PQsendQuery( fetch );
      queryResult = mConn->PQgetResult();
      int rows = queryResult.PQntuples();
      mLastFetch = rows < mFeatureQueueSize;
      for ( int row = 0; row < rows; row++ )
      {
        mFeatureQueue.enqueue( QgsFeature() );
        getFeature( queryResult, row, mFeatureQueue.back() );
      } // for each row in queue
    }// if
    .......
    // 2)
    feature = mFeatureQueue.dequeue();
    mFetched++;
    // 3)
    geometryToDestinationCrs( feature, mTransform );
    if ( mDistanceWithinEngine && mDistanceWithinEngine->distance( feature.geometry().constGet() ) > mRequest.distanceWithin() )
    {
      continue;
    }
    // 4)
    feature.setValid( true );
    feature.setFields( mSource->mFields ); // allow name-based attribute lookups
    return true;
  }// while(true)

  // 5)
  close();
  mSource->mShared->ensureFeaturesCountedAtLeast( mFetched );
}

fetchFeature()从数据源每次获取一个要素。所以该函数会被多次调用,直至要素提取完后,调用close()关闭迭代器。

  1. 如果要素队列为空并且不是最后的fetch,则从数据库获取数据,并将新feature入队,再调用getFeature()获取feature的具体数据和属性值等。
  2. 每次fetchFeature()从队列中出队一个feature,并将mFetched自增1。
  3. 将要素几何转到对应的坐标系。
  4. 设置要素的属性字段。
  5. 要素获取完后,关闭迭代器。

(5) nextFeatureFilterExpression() override

bool QgsPostgresFeatureIterator::nextFeatureFilterExpression( QgsFeature &f )
{
  if ( !mExpressionCompiled )
    return QgsAbstractFeatureIterator::nextFeatureFilterExpression( f );
  else
    return fetchFeature( f );
}

带过滤表达式的获取下一个要素,根据QgsAbstractFeatureIterator中对该接口的描述,可以在内部将该函数重定向到fetchFeature()。

(6) getFeature()

bool QgsPostgresFeatureIterator::getFeature( QgsPostgresResult &queryResult, int row, QgsFeature &feature )
{
  ......
  if ( mFetchGeometry )
  {
      ......
      // 1)
      int returnedLength = ::PQgetlength( queryResult.result(), row, col );
      if(returnedLength  > 0)
      {
        unsigned char *featureGeom = new unsigned char[returnedLength + 1];
        memcpy( featureGeom, PQgetvalue( queryResult.result(), row, col ), returnedLength );
        memset( featureGeom + returnedLength, 0, 1 );
        ......
        QgsGeometry g;
        g.fromWkb( featureGeom, returnedLength + 1 );
        feature.setGeometry( g );
        ......
      }
  }
  // 2)
  QgsFeatureId fid = 0;
  ......
  feature.setId( fid );
  
  // 3)
  // iterate attributes
  if ( subsetOfAttributes )
  {
    const auto constFetchAttributes = fetchAttributes;
    for ( int idx : constFetchAttributes )
      getFeatureAttribute( idx, queryResult, row, col, feature );
  }
  else
  {
    for ( int idx = 0; idx < mSource->mFields.count(); ++idx )
      getFeatureAttribute( idx, queryResult, row, col, feature );
  }

}

getFeature()从sql结果中获取具体的feature数据。

  1. 通过mFetchGeometry判断是否获取几何,如果要获取的话,从sql结果的第一列获取到wkb数据。调用setGeometry()设置feature的几何信息。
  2. 获取featureId。
  3. 调用getFeatureAttribute()获取要素的具体属性。

(7) getFeatureAttribute()

void QgsPostgresFeatureIterator::getFeatureAttribute( int idx, QgsPostgresResult &queryResult, int row, int &col, QgsFeature &feature )
{
  // 1)
  if ( mSource->mPrimaryKeyAttrs.contains( idx ) )
    return;
  // 2)
  const QgsField fld = mSource->mFields.at( idx );

  // 3)
  switch ( fld.type() )
  {
    case QVariant::ByteArray:
    {
     ......
       v = QgsPostgresProvider::convertValue( fld.type(), fld.subType(), queryResult.PQgetvalue( row, col ), fld.typeName(), mConn );
    }
  }  
  
  // 4)
  feature.setAttribute( idx, v );
 
}

从sql数据集里获取具体的feature属性。

  1. 主键属性则跳过。
  2. 从FeatureSource找出当前的字段。
  3. 判断字段类型,并取出属性值。
  4. 为feature设置属性值。

(8) declareCursor()

bool QgsPostgresFeatureIterator::declareCursor( const QString &whereClause, long limit, bool closeOnFail, const QString &orderBy )
{
  QString query( QStringLiteral( "SELECT " ) );
  QString delim;
  // 1)
  if ( mFetchGeometry )
  {
    ......
    geom = QStringLiteral( "%1(%2,'%3')" )
       .arg( mConn->majorVersion() < 2 ? "asbinary" : "st_asbinary",
             geom,
             QgsPostgresProvider::endianString() );
    query += delim + geom;
    ......
  }
  // 2)
  switch ( mSource->mPrimaryKeyType )
  {
    case PktOid:
      query += delim + "oid";
      delim = ',';
      break;
  ......
  }
  ......
  // 3)
  const auto constAllAttributesList = subsetOfAttributes ? mRequest.subsetOfAttributes() : mSource->mFields.allAttributesList();
  for ( int idx : constAllAttributesList )
  {
    if ( mSource->mPrimaryKeyAttrs.contains( idx ) )
      continue;

    query += delim + mConn->fieldExpression( mSource->mFields.at( idx ) );
  }

  // 4)
  query += " FROM " + mSource->mQuery;

  if ( !whereClause.isEmpty() )
    query += QStringLiteral( " WHERE %1" ).arg( whereClause );

  if ( limit >= 0 )
    query += QStringLiteral( " LIMIT %1" ).arg( limit );

  if ( !orderBy.isEmpty() )
    query += QStringLiteral( " ORDER BY %1 " ).arg( orderBy );

  if ( !mConn->openCursor( mCursorName, query ) )
  {
    // reloading the fields might help next time around
    // TODO how to cleanly force reload of fields?  P->loadFields();
    if ( closeOnFail )
      close();
    return false;
  }
}

declareCursor()在构造函数中被调用,用以开启数据库游标。

  1. 追加几何列查询。
  2. 追加主键查询语句。
  3. 追加属性字段的查询,通过mRequest的标志位判断是否查询属性子集,当进行地图缩放时,mRequest会要求不必查询属性字段,只查几何字段。当打开属性表时,才会从mRequest中获取到所有的属性字段。
  4. 完善过滤条件,开启游标。

(9) whereClauseRect()

QString QgsPostgresFeatureIterator::whereClauseRect()
{
  QgsRectangle rect = mFilterRect;

  QString qBox;
  const QString bboxSrid = mSource->mRequestedSrid.isEmpty() ? mSource->mDetectedSrid : mSource->mRequestedSrid;
  if ( mConn->majorVersion() < 2 )
  {
    qBox = QStringLiteral( "setsrid('BOX3D(%1)'::box3d,%2)" )
           .arg( rect.asWktCoordinates(),
                 bboxSrid );
  }
  else
  {
    qBox = QStringLiteral( "st_makeenvelope(%1,%2,%3,%4,%5)" )
           .arg( qgsDoubleToString( rect.xMinimum() ),
                 qgsDoubleToString( rect.yMinimum() ),
                 qgsDoubleToString( rect.xMaximum() ),
                 qgsDoubleToString( rect.yMaximum() ),
                 bboxSrid );
  }
  ......
  // 1)
  QString whereClause = QStringLiteral( "%1%2 && %3" )
                        .arg( QgsPostgresConn::quotedIdentifier( mSource->mBoundingBoxColumn ),
                              castToGeometry ? "::geometry" : "",
                              qBox );
}

whereClauseRect()增加范围过滤条件,过滤出规定范围内的要素。
在对地图进行缩放时会更新范围矩形,获取矩形范围内的要素。
使用要素拾取时,也会通过当前拾取范围过滤出范围内的全部要素从而进行要素拾取。

  1. 使用SQL的st_makeenvelope函数与shape字段进行&&操作,得到是否与矩形有交集。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容