QGIS地图渲染分析

QgsMapCanvas:QGraphicsView

|- 成员变量:

  • QGraphicsScene *mScene = nullptr;
    随构造函数一起新建,管理items。
  • QgsMapCanvasMap *mMap = nullptr;
    拥有一张图片(一个QImage成员变量),是QGraphicsItem的间接子类,主要用于展示地图。
  • QgsMapRendererQImageJob *mJob = nullptr;
    控制实际的渲染。
 QImage img = mJob->renderedImage();
 mMap->setContent( img, imageRect( img, mSettings ) );
  • QList< QgsMapRendererQImageJob * > mPreviewJobs;
    预览工作。
  • QgsMapSettings mSettings;
    包含与绘制相关的所有设置:范围、图层(QgsMapLayer)等。

跟踪mScene

1.在QgsMapCanvas构造函数中创建并setSceneRect()设置范围。
2.在析构函数中释放items()和自身。
3.在QgsMapCanvas::resizeEvent()中根据窗口变化更新矩形范围setSceneRect()
4.在QgsMapCanvas::setCanvasColor()中更新场景背景色setBackgroundBrush()

跟踪mMap

1.在构造函数中创建。
2.在QgsMapCanvas::rendererJobFinished()中将渲染好的图片设置到内容中。

 mMap->setContent( img, imageRect( img, mSettings ) );

3.在QgsMapCanvas::previewJobFinished()中添加预览图片。

 mMap->addPreviewImage( job->renderedImage(), job->mapSettings().extent() );

4.在QgsMapCanvas的定时更新任务中,更新渲染好的图片

void QgsMapCanvas::mapUpdateTimeout()
{
  if ( mJob )
  {
    const QImage &img = mJob->renderedImage();
    mMap->setContent( img, imageRect( img, mSettings ) );
  }
}

5.在QgsMapCanvas::saveAsImage()中按需提供地图图片

  else //use the map view
  {
    image = mMap->contentImage().copy();
    painter.begin( &image );
  }

跟踪mJob

1.在QgsMapCanvas::cancelJobs()中,取消渲染,并释放该对象。

  if ( mJob )
  {
    whileBlocking( mJob )->cancel();
    delete mJob;
    mJob = nullptr;
  }

2.在QgsMapCanvas::isDrawing()中通过判断该对象是否为空来判断是否正在绘制。

bool QgsMapCanvas::isDrawing()
{
  return nullptr != mJob;
} // isDrawing

3.在QgsMapCanvas::refreshMap()中,实例化该对象(并行、同步),将job的finished信号连接到QgsMapCanvas::rendererJobFinished(),最后调用start()开始渲染。

  // create the renderer job
  Q_ASSERT( !mJob );
  mJobCanceled = false;
  if ( mUseParallelRendering )
    mJob = new QgsMapRendererParallelJob( renderSettings );
  else
    mJob = new QgsMapRendererSequentialJob( renderSettings );

  connect( mJob, &QgsMapRendererJob::finished, this, &QgsMapCanvas::rendererJobFinished );
  mJob->setCache( mCache );
  mJob->setLayerRenderingTimeHints( mLastLayerRenderTime );

  mJob->start();
  1. 在QgsMapCanvas::rendererJobFinished()中获取渲染好的QImage对象(mJob->renderedImage())和其他数据(错误mJob->errors()、地图范围mJob->mapSettings().extent()、图层idmJob->mapSettings().layerIds()、渲染时长mJob->renderingTime()等)。
    最后,删除该对象。也就是说mJob在QgsMapCanvas::refreshMap()中被实例化,然后渲染结束后,在QgsMapCanvas::rendererJobFinished()中被释放。
  QImage img = mJob->renderedImage();
  ......
  // now we are in a slot called from mJob - do not delete it immediately
  // so the class is still valid when the execution returns to the class
  mJob->deleteLater();
  mJob = nullptr;

  emit mapCanvasRefreshed()

5.在QgsMapCanvas的定时任务中取出QImage对象。在mJob调用start()后,QgsMapCanvas便启动定时,并在定时任务中调用mJob->renderedImage()获取图像,直到mJob渲染结束。

void QgsMapCanvas::mapUpdateTimeout()
{
  if ( mJob )
  {
    const QImage &img = mJob->renderedImage();
    mMap->setContent( img, imageRect( img, mSettings ) );
  }
}

6.在QgsMapCanvas::stopRendering()中停止渲染

void QgsMapCanvas::stopRendering()
{
  if ( mJob )
  {
    QgsDebugMsgLevel( QStringLiteral( "CANVAS stop rendering!" ), 2 );
    mJobCanceled = true;
    disconnect( mJob, &QgsMapRendererJob::finished, this, &QgsMapCanvas::rendererJobFinished );
    connect( mJob, &QgsMapRendererQImageJob::finished, mJob, &QgsMapRendererQImageJob::deleteLater );
    mJob->cancelWithoutBlocking();
    mJob = nullptr;
    emit mapRefreshCanceled();
  }
  stopPreviewJobs();
}

地图刷新

1.refreshMap()为私有槽,与定时器mRefreshTimer绑定,该定时器为单次触发,而触发点在refresh()接口中。所有每次refresh()时触发地图刷新。

QgsMapCanvas::QgsMapCanvas()
{
 ......
  mRefreshTimer = new QTimer( this );
  mRefreshTimer->setSingleShot( true );
  connect( mRefreshTimer, &QTimer::timeout, this, &QgsMapCanvas::refreshMap );
 ......
}
void QgsMapCanvas::refresh()
{
 ......
  // schedule a refresh
  mRefreshTimer->start( 1 );
 ......
}

2.refresh()为公有槽,与定时器mResizeTimer绑定,该定时器为单次触发,在resizeEvent()中调用,以确保窗口大小改变时触发刷新。

void QgsMapCanvas::resizeEvent( QResizeEvent *e )
{
  QGraphicsView::resizeEvent( e );
  mResizeTimer->start( 500 ); // in charge of refreshing canvas
 ......
}

refresh()被多个信号连接并在多处被调用,确保地图得到及时刷新:

  • 椭球体改变:
    connect( QgsProject::instance(), &QgsProject::ellipsoidChanged,
             this, [ = ]
    {
      mSettings.setEllipsoid( QgsProject::instance()->ellipsoid() );
      refresh();
    } );
  • 转换改变
    connect( QgsProject::instance(), &QgsProject::transformContextChanged,
             this, [ = ]
    {
      mSettings.setTransformContext( QgsProject::instance()->transformContext() );
      emit transformContextChanged();
      refresh();
    } );
  • 设置放大系数
void QgsMapCanvas::setMagnificationFactor( double factor, const QgsPointXY *center )
{
  ......
  refresh();
  ......
}
  • 设置图层
void QgsMapCanvas::setLayersPrivate( const QList &layers )
  • 设置坐标系
void QgsMapCanvas::setDestinationCrs( const QgsCoordinateReferenceSystem &crs )
  • 地图设置
void QgsMapCanvas::setMapSettingsFlags( Qgis::MapSettingsFlags flags )
void QgsMapCanvas::setCanvasColor( const QColor &color )
  • 地图主题改变
void QgsMapCanvas::mapThemeChanged( const QString &theme )
void QgsMapCanvas::mapThemeRenamed( const QString &theme, const QString &newTheme )
  • 缩放至四至
void QgsMapCanvas::zoomToFullExtent()
void QgsMapCanvas::zoomToProjectExtent()
void QgsMapCanvas::zoomToPreviousExtent()
void QgsMapCanvas::zoomToNextExtent()
void QgsMapCanvas::zoomToFeatureExtent( QgsRectangle &rect )
  • 地图移动
void QgsMapCanvas::panToFeatureIds( QgsVectorLayer *layer, const QgsFeatureIds &ids, bool alwaysRecenter )
void QgsMapCanvas::panToSelected( QgsVectorLayer *layer )
void QgsMapCanvas::keyPressEvent( QKeyEvent *e )

刷新逻辑:

通过信号或主动调用(地图改变、发生平移、缩放)的方式调用refresh()接口,refresh()使用定时器触发refreshMap(),在refreshMap()中进行渲染调度。
1.停止当前的绘制工作。

  stopRendering(); // if any...
  stopPreviewJobs();

2.创建RenderJob(并行/同步),传入renderSettingsrenderSettins中包含渲染地图需要的数据。

  if ( mUseParallelRendering )
    mJob = new QgsMapRendererParallelJob( renderSettings );
  else
    mJob = new QgsMapRendererSequentialJob( renderSettings );

3.连接job的渲染结束信号至槽QgsMapCanvas::renderJobFinished,以便QgsMapCanvas获取渲染结束信号。

  connect( mJob, &QgsMapRendererJob::finished, this, &QgsMapCanvas::rendererJobFinished );
  mJob->setCache( mCache );
  mJob->setLayerRenderingTimeHints( mLastLayerRenderTime );

4.调用start()接口,开始执行渲染。

  mJob->start();

5.启动mMapUpdateTimer定时器,在mJob渲染过程中,定时去获取渲染图像(可能只渲染了部分)。如果单次绘制任务耗时较长,通过此定时任务也能及时更新出部分渲染好的地图?

  // from now on we can accept refresh requests again
  // this must be reset only after the job has been started, because
  // some providers (yes, it's you WCS and AMS!) during preparation
  // do network requests and start an internal event loop, which may
  // end up calling refresh() and would schedule another refresh,
  // deleting the one we have just started.
  mRefreshScheduled = false;

  mMapUpdateTimer.start();

  emit renderStarting();
void QgsMapCanvas::mapUpdateTimeout()
{
  if ( mJob )
  {
    const QImage &img = mJob->renderedImage();
    mMap->setContent( img, imageRect( img, mSettings ) );
  }
}

渲染

在QgsMapCanvas构造函数中将mMapUpdateTimer的超时信号连接到槽QgsMapCanvas::mapUpdateTimeout(),并设置间隔为250ms.

void QgsMapCanvas::mapUpdateTimeout()
{
  if ( mJob )
  {
    const QImage &img = mJob->renderedImage();
    mMap->setContent( img, imageRect( img, mSettings ) );
  }
}

在槽函数mapUpdateTimeout()中,调用QgsMapRendererQImageJob::renderedImage()输出渲染好的Image对象,并赋值给mMap.
QgsMapRendererQImageJob::renderedImage()是个纯虚函数,分别在子类QgsMapRendererParallelJobQgsMapRendererSequentialJob中实现。

QImage QgsMapRendererParallelJob::renderedImage()
{
  // if status == Idle we are either waiting for the render to start, OR have finished the render completely.
  // We can differentiate between those states by checking whether mFinalImage is null -- at the "waiting for
  // render to start" state mFinalImage has not yet been created.
  const bool jobIsComplete = mStatus == Idle && !mFinalImage.isNull();

  if ( !jobIsComplete )
    return composeImage( mSettings, mLayerJobs, mLabelJob, mCache );
  else
    return mFinalImage; // when rendering labels or idle
}

QImage QgsMapRendererSequentialJob::renderedImage()
{
  if ( isActive() && mCache )
    // this will allow immediate display of cached layers and at the same time updates of the layer being rendered
    return composeImage( mSettings, mInternalJob->jobs(), LabelRenderJob() );
  else
    return mImage;
}

从renderedImage()的实现可知,其通过composeImage()生成QImage对象。

QgsMapRenderJob::composeImage()

composeImage()在QgsMapRendererJob中实现。

QImage QgsMapRendererJob::composeImage( const QgsMapSettings &settings,
                                        const std::vector &jobs,
                                        const LabelRenderJob &labelJob,
                                        const QgsMapRendererCache *cache
                                      )
{
  QImage image( settings.deviceOutputSize(), settings.outputImageFormat() );
  image.setDevicePixelRatio( settings.devicePixelRatio() );
  image.setDotsPerMeterX( static_cast( settings.outputDpi() * 39.37 ) );
  image.setDotsPerMeterY( static_cast( settings.outputDpi() * 39.37 ) );
  image.fill( settings.backgroundColor().rgba() );

  QPainter painter( &image );

composeImage()中创建Image对象时,传入了QgsMapSettings::deviceOutputSize()。

QSize QgsMapSettings::deviceOutputSize() const
{
  return outputSize() * mDevicePixelRatio;
}
QSize QgsMapSettings::outputSize() const
{
  return mSize;
}
void QgsMapSettings::setOutputSize( QSize size )
{
  mSize = size;

  updateDerived();
}

QgsMapSettings::deviceOutputSize()的实现可知,其返回的size是成员变量mSize*mDevicePixelRatio。而mSize的赋值由setOutputSize()实现。
继续回到QgsMapCanvas类,查看其调用QgsMapSettings::setOutputSize()的地方:

setOutputSize()调用.png

  QSize s = viewport()->size();
  mSettings.setOutputSize( s );

传入的size大小其实是QGraphicsView的大小。
再回到QgsMapSettings中对mDevicePixelRatio的使用,对其赋值是通过setDevicePixelRatio()实现:

QgsMapSettings::mDevicePixelRatio

回到QgsMapCanvas中查找对QgsMapSettings::setDevicePixelRatio()的调用

void QgsMapCanvas::updateDevicePixelFromScreen()
{
  mSettings.setDevicePixelRatio( devicePixelRatio() );
  ......
}

其值是由QPaintDevice::devicePixelRatio()传入。

  1. 绘制图层。
 for ( const LayerRenderJob &job : jobs )
{
......
 QImage img = layerImageToBeComposed( settings, job, cache );
......
 painter.drawImage( 0, 0, img );
......
}

composeImage()中遍历传入的LayerRenderJob,通过layerImageToBeComposed()获取单个job的图像,并依次绘制。
进入layerImageToBeComposed()

QImage QgsMapRendererJob::layerImageToBeComposed(
  const QgsMapSettings &settings,
  const LayerRenderJob &job,
  const QgsMapRendererCache *cache
)
{
  if ( job.imageCanBeComposed() )
  {
    Q_ASSERT( job.img );
    return *job.img;
  }
  else
  {
    if ( cache && cache->hasAnyCacheImage( job.layerId + QStringLiteral( "_preview" ) ) )
    {
      return cache->transformedCacheImage( job.layerId + QStringLiteral( "_preview" ), settings.mapToPixel() );
    }
    else
      return QImage();
  }
}

通过判断job的图像是否可以被组合,如果可以则返回job中的图像,否则,尝试从缓冲中获取或返回空图像。

  1. 绘制Lable图层。
  // IMPORTANT - don't draw labelJob img before the label job is complete,
  // as the image is uninitialized and full of garbage before the label job
  // commences
  if ( labelJob.img && labelJob.complete )
  {
    painter.setCompositionMode( QPainter::CompositionMode_SourceOver );
    painter.setOpacity( 1.0 );
    painter.drawImage( 0, 0, *labelJob.img );
  }
  // when checking for a label cache image, we only look for those which would be drawn between 30% and 300% of the
  // original size. We don't want to draw massive pixelated labels on top of everything else, and we also don't need
  // to draw tiny unreadable labels... better to draw nothing in this case and wait till the updated label results are ready!
  else if ( cache && cache->hasAnyCacheImage( LABEL_PREVIEW_CACHE_ID, 0.3, 3 ) )
  {
    const QImage labelCacheImage = cache->transformedCacheImage( LABEL_PREVIEW_CACHE_ID, settings.mapToPixel() );
    painter.setCompositionMode( QPainter::CompositionMode_SourceOver );
    painter.setOpacity( 1.0 );
    painter.drawImage( 0, 0, labelCacheImage );
  }
  1. 绘制在Label之上的图层。
  // render any layers with the renderAboveLabels flag now
  for ( const LayerRenderJob &job : jobs )

QgsMapRendererSequentialJob

  • startPrivate()
......
mPainter = new QPainter( &mImage );
......
mInternalJob = new QgsMapRendererCustomPainterJob( mSettings, mPainter );
......
mInternalJob->start();

在startPrivate()中,实例化QPainter和QgsMapRendererCustomPainterJob,并调用mInternalJob的start()接口。所以绘制任务的开启委托给了QgsMapRendererCustomPainterJob对象。

  • QgsMapRendererCustomPainterJob::startPrivate()
......
mLayerJobs = prepareJobs( mPainter, mLabelingEngineV2.get() );
......
  if ( mRenderSynchronously )
  {
    if ( !mPrepareOnly )
    {
      // do the rendering right now!
      doRender();
    }
    return;
  }
......

QgsMapRendererCustomPainterJob通过prepareJobs()从mapSetting中提取图层信息,并得到mLayerJobs,之后调用doRender()开启绘制。

  • QgsMapRendererJob::prepareJobs()
    prepareJobs()在父类QgsMapRendererJob中实现。
......
  // render all layers in the stack, starting at the base
  QListIterator li( mSettings.layers() );
  li.toBack();
......
  while ( li.hasPrevious() )
  {
    QgsMapLayer *ml = li.previous();

    QgsVectorLayer *vl = qobject_cast( ml );
......
    layerJobs.emplace_back( LayerRenderJob() );
    LayerRenderJob &job = layerJobs.back();
    job.layer = ml;
    job.layerId = ml->id();
    job.estimatedRenderingTime = mLayerRenderingTimeHints.value( ml->id(), 0 );
......
    job.renderer = ml->createMapRenderer( *( job.context() ) );
    if ( job.renderer )
    {
      job.renderer->setLayerRenderingTimeHint( job.estimatedRenderingTime );
      job.context()->setFeedback( job.renderer->feedback() );
    }
......

首先从MapSetting中遍历图层,并将图层信息赋值给LayerRenderJob对象,然后调用maplayer的createMapRenderer()接口实例化渲染对象。

  • QgsMapRendererCustomPainterJob::doRender()
......
for ( LayerRenderJob &job : mLayerJobs )
{
......
    job.completed = job.renderer->render();
......
    if ( ! hasSecondPass && job.img )
    {
      // If we flattened this layer for alternate blend modes, composite it now
      mPainter->setOpacity( job.opacity );
      mPainter->drawImage( 0, 0, *job.img );
      mPainter->setOpacity( 1.0 );
    }

}

doRender()中遍历LayerJobs,并且通过LayerRenderJob中的renderer对象调用render()执行渲染。并将渲染好的图像保存在job中。

QsVectorLayerRenderer

以QsVectorLayerRenderer为例:

bool QgsVectorLayerRenderer::render()
{
......
  for ( const std::unique_ptr< QgsFeatureRenderer > &renderer : mRenderers )
  {
    res = renderInternal( renderer.get() ) && res;
  }
......
}
bool QgsVectorLayerRenderer::renderInternal( QgsFeatureRenderer *renderer )
{
......
  QgsFeatureIterator fit = mSource->getFeatures( featureRequest );
  // Attach an interruption checker so that iterators that have potentially
  // slow fetchFeature() implementations, such as in the WFS provider, can
  // check it, instead of relying on just the mContext.renderingStopped() check
  // in drawRenderer()
  fit.setInterruptionChecker( mFeedback.get() );

  if ( ( renderer->capabilities() & QgsFeatureRenderer::SymbolLevels ) && renderer->usingSymbolLevels() )
    drawRendererLevels( renderer, fit );
  else
    drawRenderer( renderer, fit );
......
}
void QgsVectorLayerRenderer::drawRenderer( QgsFeatureRenderer *renderer, QgsFeatureIterator &fit )
{
......
  QgsFeature fet;
  while ( fit.nextFeature( fet ) )
  {
      // render feature
      bool rendered = false;
      if ( !context.testFlag( Qgis::RenderContextFlag::SkipSymbolRendering ) )
      {
        rendered = renderer->renderFeature( fet, context, -1, sel, drawMarker );
      }
      else
      {
        rendered = renderer->willRenderFeature( fet, context );
      }
     ......
  }
}

QgsVectorLayerRenderer::render()中遍历得到QgsFeatureRenderer,再调用QgsVectorLayerRenderer::renderInternal(),在renderInternal()里遍历要素,使用要素渲染器执行要素渲染gsFeatureRenderer::renderFeature()

总结

QgsMapRendererSequentialJob将start()任务交给QgsMapRendererCustomPainterJob,然后遍历图层,依次调用layerJob的渲染器(render)进行渲染。以矢量图层(QgsVectorLayerRenderer)渲染为例,遍历要素,并调用要素的渲染器(QgsFeatureRenderer),最后交给QgsSymbol,对要素进行符号化渲染。接口调用伪代码如下所示:

QgsMapRendererSequentialJob::start()
{
  QgsMapRendererCustomPainterJob::start()
  {
    QgsMapRendererCustomPainterJob::doRender()
    {
      for(LayerRenderJob job: layerJobs)
      {
        job.renderer->render()
        { 
          for(QgsFeature feature : features)
          {
              QgsFeatureRenderer::renderFeature()
              {
                  QgsSymbol::renderFeature();
              }
          }
        }
      }
    }
  }
}

QgsMapRendererParallelJob

  • startPrivate()
  const bool canUseLabelCache = prepareLabelCache();
  mLayerJobs = prepareJobs( nullptr, mLabelingEngineV2.get() );
  mLabelJob = prepareLabelingJob( nullptr, mLabelingEngineV2.get(), canUseLabelCache );
  mSecondPassLayerJobs = prepareSecondPassJobs( mLayerJobs, mLabelJob );

  // start async job

  connect( &mFutureWatcher, &QFutureWatcher::finished, this, &QgsMapRendererParallelJob::renderLayersFinished );

  mFuture = QtConcurrent::map( mLayerJobs, renderLayerStatic );
  mFutureWatcher.setFuture( mFuture );

QgsMapRendererParallelJob在startPrivate()中使用QtConcurrent来进行并行计算。单个计算由void QgsMapRendererParallelJob::renderLayerStatic( LayerRenderJob &job )执行。

void QgsMapRendererParallelJob::renderLayerStatic( LayerRenderJob &job )
{
......
    job.completed = job.renderer->render();
......
}

renderLayerStatic()中也同QgsMapRendererCustomPainterJob一样调用layerJob的renderer进行实际渲染。

Layers

~Layers: QgsMapLayer : QObject

MapCanvasMap : QgsMapCanvasItem : QGraphicsItem
重写paint(),利用QPainter::drawImage

ps:
图元unit : QGraphicsItem
图层Layer:QGraphicsItemGroup
地图:Map:QGraphcisScene:w

你可能感兴趣的:(QGIS地图渲染分析)