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();
- 在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(并行/同步),传入renderSettings
,renderSettins
中包含渲染地图需要的数据。
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()是个纯虚函数,分别在子类QgsMapRendererParallelJob
和QgsMapRendererSequentialJob
中实现。
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()
的地方:
QSize s = viewport()->size();
mSettings.setOutputSize( s );
传入的size大小其实是QGraphicsView的大小。
再回到QgsMapSettings中对mDevicePixelRatio的使用,对其赋值是通过setDevicePixelRatio()
实现:
回到QgsMapCanvas中查找对
QgsMapSettings::setDevicePixelRatio()
的调用
void QgsMapCanvas::updateDevicePixelFromScreen()
{
mSettings.setDevicePixelRatio( devicePixelRatio() );
......
}
其值是由QPaintDevice::devicePixelRatio()
传入。
- 绘制图层。
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中的图像,否则,尝试从缓冲中获取或返回空图像。
- 绘制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 );
}
- 绘制在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