[FEATURE] First experiment with preloading

After main map canvas render is complete, render a separate image
for each adjacent map "tile" (in the background). These are shown
when panning the map to display a preview of what will be visible
when the panning operation ends.
This commit is contained in:
Marco Hugentobler 2017-06-09 17:29:37 +02:00 committed by Nyall Dawson
parent bf94df83f1
commit 2d531e5814
4 changed files with 116 additions and 1 deletions

View File

@ -503,6 +503,7 @@ void QgsMapCanvas::refreshMap()
QgsDebugMsgLevel( "CANVAS refresh!", 3 );
stopRendering(); // if any...
stopPreviewJobs();
//build the expression context
QgsExpressionContext expressionContext;
@ -624,6 +625,7 @@ void QgsMapCanvas::rendererJobFinished()
p.end();
mMap->setContent( img, imageRect( img, mSettings ) );
startPreviewJobs();
}
// now we are in a slot called from mJob - do not delete it immediately
@ -634,6 +636,20 @@ void QgsMapCanvas::rendererJobFinished()
emit mapCanvasRefreshed();
}
void QgsMapCanvas::previewJobFinished()
{
QgsMapRendererQImageJob *job = qobject_cast<QgsMapRendererQImageJob *>( sender() );
if ( !job )
{
return;
}
if ( mMap )
{
mMap->addPreviewImage( job->renderedImage(), job->mapSettings().extent() );
}
}
QgsRectangle QgsMapCanvas::imageRect( const QImage &img, const QgsMapSettings &mapSettings )
{
// This is a hack to pass QgsMapCanvasItem::setRect what it
@ -2109,3 +2125,54 @@ const QgsLabelingEngineSettings &QgsMapCanvas::labelingEngineSettings() const
{
return mSettings.labelingEngineSettings();
}
void QgsMapCanvas::startPreviewJobs()
{
stopPreviewJobs(); //just in case still running
QgsRectangle mapRect = mSettings.visibleExtent();
for ( int j = 0; j < 3; ++j )
{
for ( int i = 0; i < 3; ++i )
{
if ( i == 1 && j == 1 )
{
continue;
}
//copy settings, only update extent
QgsMapSettings jobSettings = mSettings;
double dx = ( i - 1 ) * mapRect.width();
double dy = ( 1 - j ) * mapRect.height();
QgsRectangle jobExtent = mapRect;
jobExtent.setXMaximum( jobExtent.xMaximum() + dx );
jobExtent.setXMinimum( jobExtent.xMinimum() + dx );
jobExtent.setYMaximum( jobExtent.yMaximum() + dy );
jobExtent.setYMinimum( jobExtent.yMinimum() + dy );
jobSettings.setExtent( jobExtent );
QgsMapRendererQImageJob *job = new QgsMapRendererParallelJob( jobSettings );
mPreviewJobs.append( job );
connect( job, SIGNAL( finished() ), this, SLOT( previewJobFinished() ) );
job->start();
}
}
}
void QgsMapCanvas::stopPreviewJobs()
{
QList< QgsMapRendererQImageJob * >::iterator it = mPreviewJobs.begin();
for ( ; it != mPreviewJobs.end(); ++it )
{
if ( *it )
{
( *it )->cancel();
}
delete ( *it );
}
mPreviewJobs.clear();
}

View File

@ -586,6 +586,9 @@ class GUI_EXPORT QgsMapCanvas : public QGraphicsView
//! called when a renderer job has finished successfully or when it was canceled
void rendererJobFinished();
//! called when a preview job has been finished
void previewJobFinished();
void mapUpdateTimeout();
void refreshMap();
@ -820,6 +823,8 @@ class GUI_EXPORT QgsMapCanvas : public QGraphicsView
QgsSnappingUtils *mSnappingUtils = nullptr;
QList< QgsMapRendererQImageJob * > mPreviewJobs;
//! lock the scale, so zooming can be performed using magnication
bool mScaleLocked;
@ -868,6 +873,9 @@ class GUI_EXPORT QgsMapCanvas : public QGraphicsView
void setLayersPrivate( const QList<QgsMapLayer *> &layers );
void startPreviewJobs();
void stopPreviewJobs();
friend class TestQgsMapCanvas;
}; // class QgsMapCanvas

View File

@ -16,7 +16,9 @@
#include "qgslogger.h"
#include "qgsmapcanvas.h"
#include "qgsmapcanvasmap.h"
#include "qgsmaprendererjob.h"
#include "qgsmapsettings.h"
#include "qgsmaplayer.h"
#include <QPainter>
@ -30,6 +32,8 @@ QgsMapCanvasMap::QgsMapCanvasMap( QgsMapCanvas *canvas )
void QgsMapCanvasMap::setContent( const QImage &image, const QgsRectangle &rect )
{
mPreviewImages.clear();
mImage = image;
// For true retro fans: this is approximately how the graphics looked like in 1990
@ -40,9 +44,23 @@ void QgsMapCanvasMap::setContent( const QImage &image, const QgsRectangle &rect
setRect( rect );
}
void QgsMapCanvasMap::addPreviewImage( const QImage &image, const QgsRectangle &rect )
{
mPreviewImages.append( qMakePair( image, rect ) );
update();
}
QRectF QgsMapCanvasMap::boundingRect() const
{
double width = mItemSize.width();
double height = mItemSize.height();
return QRectF( -width, -height, 3 * width, 3 * height );
}
void QgsMapCanvasMap::paint( QPainter *painter )
{
int w = qRound( boundingRect().width() ) - 2, h = qRound( boundingRect().height() ) - 2; // setRect() makes the size +2 :-(
int w = qRound( mItemSize.width() ) - 2, h = qRound( mItemSize.height() ) - 2; // setRect() makes the size +2 :-(
if ( mImage.size() != QSize( w, h ) )
{
QgsDebugMsg( QString( "map paint DIFFERENT SIZE: img %1,%2 item %3,%4" ).arg( mImage.width() ).arg( mImage.height() ).arg( w ).arg( h ) );
@ -50,6 +68,21 @@ void QgsMapCanvasMap::paint( QPainter *painter )
// the renderer has completed
}
/*Offset between 0/0 and mRect.xMinimum/mRect.yMinimum.
We need to consider the offset, because mRect is not updated yet and there might be an offset*/
QgsPoint pt = toMapCoordinates( QPoint( 0, 0 ) );
double offsetX = pt.x() - mRect.xMinimum();
double offsetY = pt.y() - mRect.yMaximum();
//draw preview images first
QMap< QgsRectangle, QImage >::const_iterator previewIt = mPreviewImages.constBegin();
for ( ; previewIt != mPreviewImages.constEnd(); ++previewIt )
{
QPointF ul = toCanvasCoordinates( QgsPoint( previewIt.key().xMinimum() + offsetX, previewIt.key().yMaximum() + offsetY ) );
QPointF lr = toCanvasCoordinates( QgsPoint( previewIt.key().xMaximum() + offsetX, previewIt.key().yMinimum() + offsetY ) );
painter->drawImage( QRectF( ul.x(), ul.y(), lr.x() - ul.x(), lr.y() - ul.y() ), previewIt.value(), QRect( 0, 0, previewIt.value().width(), previewIt.value().height() ) );
}
painter->drawImage( QRect( 0, 0, w, h ), mImage );
// For debugging:

View File

@ -45,9 +45,16 @@ class QgsMapCanvasMap : public QgsMapCanvasItem
virtual void paint( QPainter *painter ) override;
void addPreviewImage( const QImage &image, const QgsRectangle &rect );
QRectF boundingRect() const;
private:
QImage mImage;
//! Preview images for panning. Usually cover area around the rendered image
QList< QPair< QImage, QgsRectangle > > mPreviewImages;
};
/// @endcond