@@ -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 ) ;
506506static 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+
508642QImage *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() );
0 commit comments