@@ -502,9 +502,143 @@ QImage *QgsWmsProvider::draw( QgsRectangle const &viewExtent, int pixelWidth, in
502
502
}
503
503
504
504
#include < QCache>
505
- static QCache<QUrl, QImage> sTileCache ;
505
+ static QCache<QUrl, QImage> sTileCache ( 256 ) ;
506
506
static QMutex sTileCacheMutex ;
507
507
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
+
508
642
QImage *QgsWmsProvider::draw ( QgsRectangle const & viewExtent, int pixelWidth, int pixelHeight, QgsRasterBlockFeedback* feedback )
509
643
{
510
644
QgsDebugMsg ( " Entering." );
@@ -642,68 +776,99 @@ QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, i
642
776
643
777
emit statusChanged ( tr ( " Getting tiles." ) );
644
778
779
+ QList<TileImage> tileImages; // in the correct resolution
780
+ QList<QRectF> missing; // rectangles (in map coords) of missing tiles for this view
781
+
645
782
QTime t;
646
783
t.start ();
647
- int memCached = 0 , diskCached = 0 ;
648
784
TileRequests requestsFinal;
649
785
Q_FOREACH ( const TileRequest& r, requests )
650
786
{
651
787
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 ) )
678
789
{
679
790
double cr = viewExtent.width () / image->width ();
680
791
681
792
QRectF dst (( r.rect .left () - viewExtent.xMinimum () ) / cr,
682
793
( viewExtent.yMaximum () - r.rect .bottom () ) / cr,
683
794
r.rect .width () / cr,
684
795
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 );
690
797
}
691
798
else
692
799
{
800
+ missing << r.rect ;
801
+
693
802
// need to make a request
694
803
requestsFinal << r;
695
804
}
696
805
}
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;
697
862
698
863
if ( feedback && feedback->preview_only )
699
864
{
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 );
702
867
}
703
868
else if ( !requestsFinal.isEmpty () )
704
869
{
705
870
// let the feedback object know about the tiles we have already
706
- if ( feedback && memCached + diskCached > 0 )
871
+ if ( feedback && feedback-> render_partial_output )
707
872
feedback->onNewData ();
708
873
709
874
// order tile requests according to the distance from view center
@@ -715,6 +880,7 @@ QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, i
715
880
handler.downloadBlocking ();
716
881
}
717
882
883
+ qDebug ( " TILE CACHE total: %d / %d " , sTileCache .totalCost (), sTileCache .maxCost () );
718
884
719
885
#if 0
720
886
const QgsWmsStatistics::Stat& stat = QgsWmsStatistics::statForUri( dataSourceUri() );
0 commit comments