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)等。
跟踪mScene1.在QgsMapCanvas构造函数中创建并setSceneRect() 设置范围。 2.在析构函数中释放items() 和自身。 3.在QgsMapCanvas::resizeEvent()中根据窗口变化更新矩形范围setSceneRect() 。 4.在QgsMapCanvas::setCanvasColor()中更新场景背景色setBackgroundBrush() 。 跟踪mMap1.在构造函数中创建。 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 ); }
跟踪mJob1.在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......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 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 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |