Skip to content

Commit 02a9211

Browse files
committedSep 9, 2016
Use cached tiles from other resolutions if available
This makes it easier to get at least basic preview without having to wait until new tiles are downloaded. The strategy is to use tiles up to two levels lower resolution tiles and one level higher resolution tiles.
1 parent beb5d00 commit 02a9211

File tree

4 files changed

+237
-45
lines changed

4 files changed

+237
-45
lines changed
 

‎src/providers/wms/qgswmscapabilities.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2151,3 +2151,15 @@ const QgsWmtsTileMatrix* QgsWmtsTileMatrixSet::findNearestResolution( double vre
21512151

21522152
return &it.value();
21532153
}
2154+
2155+
const QgsWmtsTileMatrix *QgsWmtsTileMatrixSet::findOtherResolution( double tres, int offset ) const
2156+
{
2157+
QMap<double, QgsWmtsTileMatrix>::const_iterator it = tileMatrices.constFind( tres );
2158+
if ( it == tileMatrices.constEnd() )
2159+
return nullptr;
2160+
it += offset;
2161+
if ( it == tileMatrices.constEnd() )
2162+
return nullptr;
2163+
2164+
return &it.value();
2165+
}

‎src/providers/wms/qgswmscapabilities.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,9 @@ struct QgsWmtsTileMatrixSet
350350

351351
//! Returns closest tile resolution to the requested one. (resolution = width [map units] / with [pixels])
352352
const QgsWmtsTileMatrix* findNearestResolution( double vres ) const;
353+
354+
//! Return tile matrix for other near resolution from given tres (positive offset = lower resolution tiles)
355+
const QgsWmtsTileMatrix* findOtherResolution( double tres, int offset ) const;
353356
};
354357

355358
enum QgsTileMode { WMTS, WMSC, XYZ };

‎src/providers/wms/qgswmsprovider.cpp

Lines changed: 202 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -502,9 +502,143 @@ QImage *QgsWmsProvider::draw( QgsRectangle const &viewExtent, int pixelWidth, in
502502
}
503503

504504
#include <QCache>
505-
static QCache<QUrl, QImage> sTileCache;
505+
static QCache<QUrl, QImage> sTileCache( 256 );
506506
static QMutex sTileCacheMutex;
507507

508+
static bool _fetchCachedTileImage( const QUrl& url, QImage& localImage )
509+
{
510+
QMutexLocker locker( &sTileCacheMutex );
511+
if ( QImage* i = sTileCache.object( url ) )
512+
{
513+
localImage = *i;
514+
return true;
515+
}
516+
else if ( QgsNetworkAccessManager::instance()->cache()->metaData( url ).isValid() )
517+
{
518+
if ( QIODevice* data = QgsNetworkAccessManager::instance()->cache()->data( url ) )
519+
{
520+
QByteArray imageData = data->readAll();
521+
delete data;
522+
523+
localImage = QImage::fromData( imageData );
524+
525+
// cache it as well (mutex is already locked)
526+
sTileCache.insert( url, new QImage( localImage ) );
527+
528+
return true;
529+
}
530+
}
531+
return false;
532+
}
533+
534+
static bool _fuzzyContainsRect( const QRectF& r1, const QRectF& r2 )
535+
{
536+
double significantDigits = log10( qMax( r1.width(), r1.height() ) );
537+
double epsilon = exp10( significantDigits - 5 ); // floats have 6-9 significant digits
538+
return r1.contains( r2.adjusted( epsilon, epsilon, -epsilon, -epsilon ) );
539+
}
540+
541+
void QgsWmsProvider::fetchOtherResTiles( QgsTileMode tileMode, const QgsRectangle& viewExtent, int imageWidth, QList<QRectF>& missingRects, double tres, int resOffset, QList<TileImage>& otherResTiles )
542+
{
543+
const QgsWmtsTileMatrix* tmOther = mTileMatrixSet->findOtherResolution( tres, resOffset );
544+
if ( !tmOther )
545+
return;
546+
547+
QSet<TilePosition> tilesSet;
548+
Q_FOREACH ( const QRectF& missingTileRect, missingRects )
549+
{
550+
int c0, r0, c1, r1;
551+
tmOther->viewExtentIntersection( QgsRectangle( missingTileRect ), nullptr, c0, r0, c1, r1 );
552+
553+
for ( int row = r0; row <= r1; row++ )
554+
{
555+
for ( int col = c0; col <= c1; col++ )
556+
{
557+
tilesSet << TilePosition( row, col );
558+
}
559+
}
560+
}
561+
562+
// get URLs of tiles because their URLs are used as keys in the tile cache
563+
TilePositions tiles = tilesSet.toList();
564+
TileRequests requests;
565+
switch ( tileMode )
566+
{
567+
case WMSC:
568+
createTileRequestsWMSC( tmOther, tiles, requests );
569+
break;
570+
571+
case WMTS:
572+
createTileRequestsWMTS( tmOther, tiles, requests );
573+
break;
574+
575+
case XYZ:
576+
createTileRequestsXYZ( tmOther, tiles, requests );
577+
break;
578+
}
579+
580+
QList<QRectF> missingRectsToDelete;
581+
Q_FOREACH ( const TileRequest& r, requests )
582+
{
583+
QImage localImage;
584+
if ( !_fetchCachedTileImage( r.url, localImage ) )
585+
continue;
586+
587+
double cr = viewExtent.width() / imageWidth;
588+
QRectF dst(( r.rect.left() - viewExtent.xMinimum() ) / cr,
589+
( viewExtent.yMaximum() - r.rect.bottom() ) / cr,
590+
r.rect.width() / cr,
591+
r.rect.height() / cr );
592+
otherResTiles << TileImage( dst, localImage );
593+
594+
// see if there are any missing rects that are completely covered by this tile
595+
Q_FOREACH ( const QRectF& missingRect, missingRects )
596+
{
597+
// we need to do a fuzzy "contains" check because the coordinates may not align perfectly
598+
// due to numerical errors and/or transform of coords from double to floats
599+
if ( _fuzzyContainsRect( r.rect, missingRect ) )
600+
{
601+
missingRectsToDelete << missingRect;
602+
}
603+
}
604+
}
605+
606+
// remove all the rectangles we have completely covered by tiles from this resolution
607+
// so we will not use tiles from multiple resolutions for one missing tile (to save time)
608+
Q_FOREACH ( const QRectF& rectToDelete, missingRectsToDelete )
609+
{
610+
missingRects.removeOne( rectToDelete );
611+
}
612+
613+
QgsDebugMsg( QString( "Other resolution tiles: offset %1, res %2, missing rects %3, remaining rects %4, added tiles %5" )
614+
.arg( resOffset )
615+
.arg( tmOther->tres )
616+
.arg( missingRects.count() + missingRectsToDelete.count() )
617+
.arg( missingRects.count() )
618+
.arg( otherResTiles.count() ) );
619+
}
620+
621+
uint qHash( const QgsWmsProvider::TilePosition& tp )
622+
{
623+
return ( uint ) tp.col + (( uint ) tp.row << 16 );
624+
}
625+
626+
static void _drawDebugRect( QPainter& p, const QRectF& rect, const QColor& color )
627+
{
628+
#if 0 // good for debugging how tiles from various resolutions are used
629+
QPainter::CompositionMode oldMode = p.compositionMode();
630+
p.setCompositionMode( QPainter::CompositionMode_SourceOver );
631+
QColor c = color;
632+
c.setAlpha( 100 );
633+
p.fillRect( rect, QBrush( c, Qt::DiagCrossPattern ) );
634+
p.setCompositionMode( oldMode );
635+
#else
636+
Q_UNUSED( p );
637+
Q_UNUSED( rect );
638+
Q_UNUSED( color );
639+
#endif
640+
}
641+
508642
QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, int pixelHeight, QgsRasterBlockFeedback* feedback )
509643
{
510644
QgsDebugMsg( "Entering." );
@@ -642,68 +776,99 @@ QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, i
642776

643777
emit statusChanged( tr( "Getting tiles." ) );
644778

779+
QList<TileImage> tileImages; // in the correct resolution
780+
QList<QRectF> missing; // rectangles (in map coords) of missing tiles for this view
781+
645782
QTime t;
646783
t.start();
647-
int memCached = 0, diskCached = 0;
648784
TileRequests requestsFinal;
649785
Q_FOREACH ( const TileRequest& r, requests )
650786
{
651787
QImage localImage;
652-
653-
sTileCacheMutex.lock();
654-
if ( QImage* i = sTileCache.object( r.url ) )
655-
{
656-
localImage = *i;
657-
memCached++;
658-
}
659-
else if ( QgsNetworkAccessManager::instance()->cache()->metaData( r.url ).isValid() )
660-
{
661-
if ( QIODevice* data = QgsNetworkAccessManager::instance()->cache()->data( r.url ) )
662-
{
663-
QByteArray imageData = data->readAll();
664-
delete data;
665-
666-
localImage = QImage::fromData( imageData );
667-
668-
// cache it as well (mutex is already locked)
669-
sTileCache.insert( r.url, new QImage( localImage ) );
670-
671-
diskCached++;
672-
}
673-
}
674-
sTileCacheMutex.unlock();
675-
676-
// draw the tile directly if possible
677-
if ( !localImage.isNull() )
788+
if ( _fetchCachedTileImage( r.url, localImage ) )
678789
{
679790
double cr = viewExtent.width() / image->width();
680791

681792
QRectF dst(( r.rect.left() - viewExtent.xMinimum() ) / cr,
682793
( viewExtent.yMaximum() - r.rect.bottom() ) / cr,
683794
r.rect.width() / cr,
684795
r.rect.height() / cr );
685-
686-
QPainter p( image );
687-
if ( mSettings.mSmoothPixmapTransform )
688-
p.setRenderHint( QPainter::SmoothPixmapTransform, true );
689-
p.drawImage( dst, localImage );
796+
tileImages << TileImage( dst, localImage );
690797
}
691798
else
692799
{
800+
missing << r.rect;
801+
693802
// need to make a request
694803
requestsFinal << r;
695804
}
696805
}
806+
int t0 = t.elapsed();
807+
808+
809+
// draw other res tiles if preview
810+
QPainter p( image );
811+
if ( feedback && feedback->preview_only && missing.count() > 0 )
812+
{
813+
// some tiles are still missing, so let's see if we have any cached tiles
814+
// from lower or higher resolution available to give the user a bit of context
815+
// while loading the right resolution
816+
817+
p.setCompositionMode( QPainter::CompositionMode_Source );
818+
p.fillRect( image->rect(), QBrush( Qt::lightGray, Qt::CrossPattern ) );
819+
p.setRenderHint( QPainter::SmoothPixmapTransform, false ); // let's not waste time with bilinear filtering
820+
821+
QList<TileImage> lowerResTiles, lowerResTiles2, higherResTiles;
822+
// first we check lower resolution tiles: one level back, then two levels back (if there is still some are not covered),
823+
// finally (in the worst case we use one level higher resolution tiles). This heuristic should give
824+
// good overviews while not spending too much time drawing cached tiles from resolutions far away.
825+
fetchOtherResTiles( tileMode, viewExtent, image->width(), missing, tm->tres, 1, lowerResTiles );
826+
fetchOtherResTiles( tileMode, viewExtent, image->width(), missing, tm->tres, 2, lowerResTiles2 );
827+
fetchOtherResTiles( tileMode, viewExtent, image->width(), missing, tm->tres, -1, higherResTiles );
828+
829+
// draw the cached tiles lowest to highest resolution
830+
Q_FOREACH ( const TileImage& ti, lowerResTiles2 )
831+
{
832+
p.drawImage( ti.rect, ti.img );
833+
_drawDebugRect( p, ti.rect, Qt::blue );
834+
}
835+
Q_FOREACH ( const TileImage& ti, lowerResTiles )
836+
{
837+
p.drawImage( ti.rect, ti.img );
838+
_drawDebugRect( p, ti.rect, Qt::yellow );
839+
}
840+
Q_FOREACH ( const TileImage& ti, higherResTiles )
841+
{
842+
p.drawImage( ti.rect, ti.img );
843+
_drawDebugRect( p, ti.rect, Qt::red );
844+
}
845+
}
846+
847+
int t1 = t.elapsed() - t0;
848+
849+
// draw composite in this resolution
850+
Q_FOREACH ( const TileImage& ti, tileImages )
851+
{
852+
if ( mSettings.mSmoothPixmapTransform )
853+
p.setRenderHint( QPainter::SmoothPixmapTransform, true );
854+
p.drawImage( ti.rect, ti.img );
855+
856+
if ( feedback && feedback->preview_only )
857+
_drawDebugRect( p, ti.rect, Qt::green );
858+
}
859+
p.end();
860+
861+
int t2 = t.elapsed() - t1;
697862

698863
if ( feedback && feedback->preview_only )
699864
{
700-
qDebug( "PREVIEW - MEM CACHED: %d / DISK CACHED: %d / MISSING: %d", memCached, diskCached, requests.count() - memCached - diskCached );
701-
qDebug( "PREVIEW - SPENT IN WMTS PROVIDER: %d ms", t.elapsed() );
865+
qDebug( "PREVIEW - CACHED: %d / MISSING: %d", tileImages.count(), requests.count() - tileImages.count() );
866+
qDebug( "PREVIEW - TIME: this res %d ms | other res %d ms | TOTAL %d ms", t0 + t2, t1, t0 + t1 + t2 );
702867
}
703868
else if ( !requestsFinal.isEmpty() )
704869
{
705870
// let the feedback object know about the tiles we have already
706-
if ( feedback && memCached + diskCached > 0 )
871+
if ( feedback && feedback->render_partial_output )
707872
feedback->onNewData();
708873

709874
// order tile requests according to the distance from view center
@@ -715,6 +880,7 @@ QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, i
715880
handler.downloadBlocking();
716881
}
717882

883+
qDebug( "TILE CACHE total: %d / %d ", sTileCache.totalCost(), sTileCache.maxCost() );
718884

719885
#if 0
720886
const QgsWmsStatistics::Stat& stat = QgsWmsStatistics::statForUri( dataSourceUri() );

‎src/providers/wms/qgswmsprovider.h

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,16 @@ class QgsWmsProvider : public QgsRasterDataProvider
362362
};
363363
typedef QList<TileRequest> TileRequests;
364364

365+
//! Tile identifier within a tile source
366+
typedef struct TilePosition
367+
{
368+
TilePosition( int r, int c ): row( r ), col( c ) {}
369+
bool operator==( const TilePosition& other ) const { return row == other.row && col == other.col; }
370+
int row;
371+
int col;
372+
} TilePosition;
373+
typedef QList<TilePosition> TilePositions;
374+
365375
signals:
366376

367377
/** \brief emit a signal to notify of a progress event */
@@ -441,20 +451,21 @@ class QgsWmsProvider : public QgsRasterDataProvider
441451

442452
private:
443453

444-
//! Tile identifier within a tile source
445-
typedef struct TilePosition
446-
{
447-
TilePosition( int r, int c ): row( r ), col( c ) {}
448-
int row;
449-
int col;
450-
} TilePosition;
451-
typedef QList<TilePosition> TilePositions;
452-
453454
QUrl createRequestUrlWMS( const QgsRectangle& viewExtent, int pixelWidth, int pixelHeight );
454455
void createTileRequestsWMSC( const QgsWmtsTileMatrix* tm, const QgsWmsProvider::TilePositions& tiles, QgsWmsProvider::TileRequests& requests );
455456
void createTileRequestsWMTS( const QgsWmtsTileMatrix* tm, const QgsWmsProvider::TilePositions& tiles, QgsWmsProvider::TileRequests& requests );
456457
void createTileRequestsXYZ( const QgsWmtsTileMatrix* tm, const QgsWmsProvider::TilePositions& tiles, QgsWmsProvider::TileRequests& requests );
457458

459+
//! Helper structure to store a cached tile image with its rectangle
460+
typedef struct TileImage
461+
{
462+
TileImage( QRectF r, QImage i ): rect( r ), img( i ) {}
463+
QRectF rect; //!< destination rectangle for a tile (in screen coordinates)
464+
QImage img; //!< cached tile to be drawn
465+
} TileImage;
466+
//! Get tiles from a different resolution to cover the missing areas
467+
void fetchOtherResTiles( QgsTileMode tileMode, const QgsRectangle& viewExtent, int imageWidth, QList<QRectF>& missing, double tres, int resOffset, QList<TileImage> &otherResTiles );
468+
458469
/** Return the full url to request legend graphic
459470
* The visibleExtent isi only used if provider supports contextual
460471
* legends according to the QgsWmsSettings

0 commit comments

Comments
 (0)
Please sign in to comment.