From 6214f6d4b542a6e11a139865dcce833e126d0459 Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Thu, 25 Aug 2016 15:18:58 +0800 Subject: [PATCH 01/26] WMTS preview functionality - early work --- src/core/raster/qgsrasterinterface.h | 11 +- src/core/raster/qgsrasterlayerrenderer.cpp | 29 +++- src/core/raster/qgsrasterlayerrenderer.h | 21 ++- src/providers/wms/qgswmsprovider.cpp | 163 ++++++++++++++------- src/providers/wms/qgswmsprovider.h | 22 +-- 5 files changed, 169 insertions(+), 77 deletions(-) diff --git a/src/core/raster/qgsrasterinterface.h b/src/core/raster/qgsrasterinterface.h index 0dabcfbc5b2d..b0b53bec4bd0 100644 --- a/src/core/raster/qgsrasterinterface.h +++ b/src/core/raster/qgsrasterinterface.h @@ -36,7 +36,16 @@ */ class CORE_EXPORT QgsRasterBlockFeedback : public QgsFeedback { - // TODO: extend with preview functionality?? + public: + QgsRasterBlockFeedback( QObject* parent = nullptr ) : QgsFeedback( parent ), preview_only( false ) {} + + //! whether the raster provider should return only data that are already available + //! without waiting for full result + bool preview_only; + + //! may be emitted by raster data provider to indicate that some partial data are available + //! and a new preview image may be produced + virtual void onNewData() {} }; diff --git a/src/core/raster/qgsrasterlayerrenderer.cpp b/src/core/raster/qgsrasterlayerrenderer.cpp index add0dd3259f4..3e72a787f901 100644 --- a/src/core/raster/qgsrasterlayerrenderer.cpp +++ b/src/core/raster/qgsrasterlayerrenderer.cpp @@ -29,7 +29,7 @@ QgsRasterLayerRenderer::QgsRasterLayerRenderer( QgsRasterLayer* layer, QgsRender , mRasterViewPort( nullptr ) , mPipe( nullptr ) , mContext( rendererContext ) - , mFeedback( new QgsRasterBlockFeedback() ) + , mFeedback( new MyFeedback( this ) ) { mPainter = rendererContext.painter(); const QgsMapToPixel& theQgsMapToPixel = rendererContext.mapToPixel(); @@ -226,3 +226,30 @@ QgsFeedback* QgsRasterLayerRenderer::feedback() const { return mFeedback; } + +MyFeedback::MyFeedback( QgsRasterLayerRenderer *r ) + : mR( r ) + , mMinimalPreviewInterval( 250 ) +{ +} + +void MyFeedback::onNewData() +{ + qDebug( "\nGOT NEW DATA!\n" ); + + // update only once upon a time + // (preview itself takes some time) + if ( mLastPreview.msecsTo( QTime::currentTime() ) < mMinimalPreviewInterval ) + return; + + qDebug( "new raster preview! %d", mLastPreview.msecsTo( QTime::currentTime() ) ); + QTime t; + t.start(); + QgsRasterBlockFeedback feedback; + feedback.preview_only = true; + QgsRasterIterator iterator( mR->mPipe->last() ); + QgsRasterDrawer drawer( &iterator ); + drawer.draw( mR->mPainter, mR->mRasterViewPort, mR->mMapToPixel, &feedback ); + qDebug( "PREVIEW TOOK %d ms", t.elapsed() ); + mLastPreview = QTime::currentTime(); +} diff --git a/src/core/raster/qgsrasterlayerrenderer.h b/src/core/raster/qgsrasterlayerrenderer.h index c85a9e195d3e..b4c30e3115b0 100644 --- a/src/core/raster/qgsrasterlayerrenderer.h +++ b/src/core/raster/qgsrasterlayerrenderer.h @@ -27,6 +27,23 @@ class QgsRasterPipe; struct QgsRasterViewPort; class QgsRenderContext; +class QgsRasterLayerRenderer; + +#include "qgsrasterinterface.h" + +class MyFeedback : public QgsRasterBlockFeedback +{ + public: + explicit MyFeedback( QgsRasterLayerRenderer* r ); + + virtual void onNewData() override; + private: + QgsRasterLayerRenderer* mR; + int mMinimalPreviewInterval; //!< in miliseconds + QTime mLastPreview; +}; + + /** \ingroup core * Implementation of threaded rendering for raster layers. * @@ -52,7 +69,9 @@ class QgsRasterLayerRenderer : public QgsMapLayerRenderer QgsRasterPipe* mPipe; QgsRenderContext& mContext; - QgsRasterBlockFeedback* mFeedback; + MyFeedback* mFeedback; + friend class MyFeedback; }; + #endif // QGSRASTERLAYERRENDERER_H diff --git a/src/providers/wms/qgswmsprovider.cpp b/src/providers/wms/qgswmsprovider.cpp index ca6ebb1a8a9e..f05f394ddfe9 100644 --- a/src/providers/wms/qgswmsprovider.cpp +++ b/src/providers/wms/qgswmsprovider.cpp @@ -85,11 +85,7 @@ QgsWmsProvider::QgsWmsProvider( QString const& uri, const QgsWmsCapabilities* ca , mGetLegendGraphicImage() , mGetLegendGraphicScale( 0.0 ) , mImageCrs( DEFAULT_LATLON_CRS ) - , mCachedImage( nullptr ) , mIdentifyReply( nullptr ) - , mCachedViewExtent( 0 ) - , mCachedViewWidth( 0 ) - , mCachedViewHeight( 0 ) , mExtentDirty( true ) , mTileReqNo( 0 ) , mTileLayer( nullptr ) @@ -170,13 +166,6 @@ QString QgsWmsProvider::prepareUri( QString uri ) QgsWmsProvider::~QgsWmsProvider() { QgsDebugMsg( "deconstructing." ); - - // Dispose of any cached image as created by draw() - if ( mCachedImage ) - { - delete mCachedImage; - mCachedImage = nullptr; - } } QgsWmsProvider* QgsWmsProvider::clone() const @@ -484,38 +473,22 @@ QImage *QgsWmsProvider::draw( QgsRectangle const &viewExtent, int pixelWidth, in return draw( viewExtent, pixelWidth, pixelHeight, nullptr ); } +#include +static QCache sTileCache; +static QMutex sTileCacheMutex; + QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, int pixelHeight, QgsRasterBlockFeedback* feedback ) { QgsDebugMsg( "Entering." ); - // Can we reuse the previously cached image? - if ( mCachedImage && - mCachedViewExtent == viewExtent && - mCachedViewWidth == pixelWidth && - mCachedViewHeight == pixelHeight ) - { - return mCachedImage; - } - - // delete cached image and create network request(s) to fill it - if ( mCachedImage ) - { - delete mCachedImage; - mCachedImage = nullptr; - } - - bool changeXY = mCaps.shouldInvertAxisOrientation( mImageCrs ); // compose the URL query string for the WMS server. QString bbox = toParamValue( viewExtent, changeXY ); - mCachedImage = new QImage( pixelWidth, pixelHeight, QImage::Format_ARGB32 ); - mCachedImage->fill( 0 ); - mCachedViewExtent = viewExtent; - mCachedViewWidth = pixelWidth; - mCachedViewHeight = pixelHeight; + QImage* image = new QImage( pixelWidth, pixelHeight, QImage::Format_ARGB32 ); + image->fill( 0 ); if ( !mSettings.mTiled && mSettings.mMaxWidth == 0 && mSettings.mMaxHeight == 0 ) { @@ -585,7 +558,7 @@ QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, i emit statusChanged( tr( "Getting map via WMS." ) ); - QgsWmsImageDownloadHandler handler( dataSourceUri(), url, mSettings.authorization(), mCachedImage, feedback ); + QgsWmsImageDownloadHandler handler( dataSourceUri(), url, mSettings.authorization(), image, feedback ); handler.downloadBlocking(); } else @@ -642,7 +615,7 @@ QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, i else { QgsDebugMsg( "empty tile size" ); - return mCachedImage; + return image; } QgsDebugMsg( QString( "layer extent: %1,%2 %3x%4" ) @@ -705,7 +678,7 @@ QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, i if ( n > 100 ) { emit statusChanged( QString( "current view would need %1 tiles. tile request per draw limited to 100." ).arg( n ) ); - return mCachedImage; + return image; } #endif @@ -835,7 +808,8 @@ QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, i turl.replace( "{tilerow}", QString::number( row ), Qt::CaseInsensitive ); turl.replace( "{tilecol}", QString::number( col ), Qt::CaseInsensitive ); - QgsDebugMsg( QString( "tileRequest %1 %2/%3 (%4,%5): %6" ).arg( mTileReqNo ).arg( i++ ).arg( n ).arg( row ).arg( col ).arg( turl ) ); + if ( feedback && !feedback->preview_only ) + QgsDebugMsg( QString( "tileRequest %1 %2/%3 (%4,%5): %6" ).arg( mTileReqNo ).arg( i++ ).arg( n ).arg( row ).arg( col ).arg( turl ) ); QRectF rect( tm->topLeft.x() + col * twMap, tm->topLeft.y() - ( row + 1 ) * thMap, twMap, thMap ); requests << QgsWmsTiledImageDownloadHandler::TileRequest( turl, rect, i ); } @@ -846,13 +820,78 @@ QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, i default: QgsDebugMsg( QString( "unexpected tile mode %1" ).arg( mTileLayer->tileMode ) ); - return mCachedImage; + return image; } emit statusChanged( tr( "Getting tiles." ) ); - QgsWmsTiledImageDownloadHandler handler( dataSourceUri(), mSettings.authorization(), mTileReqNo, requests, mCachedImage, mCachedViewExtent, mSettings.mSmoothPixmapTransform, feedback ); - handler.downloadBlocking(); + QTime t; + t.start(); + int memCached = 0, diskCached = 0; + QList requestsFinal; + Q_FOREACH ( const QgsWmsTiledImageDownloadHandler::TileRequest& r, requests ) + { + QImage localImage; + + sTileCacheMutex.lock(); + if ( QImage* i = sTileCache.object( r.url ) ) + { + localImage = *i; + memCached++; + } + else if ( QgsNetworkAccessManager::instance()->cache()->metaData( r.url ).isValid() ) + { + if ( QIODevice* data = QgsNetworkAccessManager::instance()->cache()->data( r.url ) ) + { + QByteArray imageData = data->readAll(); + delete data; + + localImage = QImage::fromData( imageData ); + + // cache it as well (mutex is already locked) + sTileCache.insert( r.url, new QImage( localImage ) ); + + diskCached++; + } + } + sTileCacheMutex.unlock(); + + // draw the tile directly if possible + if ( !localImage.isNull() ) + { + double cr = viewExtent.width() / image->width(); + + QRectF dst(( r.rect.left() - viewExtent.xMinimum() ) / cr, + ( viewExtent.yMaximum() - r.rect.bottom() ) / cr, + r.rect.width() / cr, + r.rect.height() / cr ); + + QPainter p( image ); + if ( mSettings.mSmoothPixmapTransform ) + p.setRenderHint( QPainter::SmoothPixmapTransform, true ); + p.drawImage( dst, localImage ); + } + else + { + // need to make a request + requestsFinal << r; + } + } + + if ( feedback && feedback->preview_only ) + { + qDebug( "PREVIEW - MEM CACHED: %d / DISK CACHED: %d / MISSING: %d", memCached, diskCached, requests.count() - memCached - diskCached ); + qDebug( "PREVIEW - SPENT IN WMTS PROVIDER: %d ms", t.elapsed() ); + } + else if ( !requestsFinal.isEmpty() ) + { + // let the feedback object know about the tiles we have already + if ( feedback && memCached + diskCached > 0 ) + feedback->onNewData(); + + QgsWmsTiledImageDownloadHandler handler( dataSourceUri(), mSettings.authorization(), mTileReqNo, requestsFinal, image, viewExtent, mSettings.mSmoothPixmapTransform, feedback ); + handler.downloadBlocking(); + } #if 0 @@ -865,7 +904,7 @@ QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, i #endif } - return mCachedImage; + return image; } void QgsWmsProvider::readBlock( int bandNo, QgsRectangle const & viewExtent, int pixelWidth, int pixelHeight, void *block, QgsRasterBlockFeedback* feedback ) @@ -885,6 +924,7 @@ void QgsWmsProvider::readBlock( int bandNo, QgsRectangle const & viewExtent, in if ( myExpectedSize != myImageSize ) // should not happen { QgsMessageLog::logMessage( tr( "unexpected image size" ), tr( "WMS" ) ); + delete image; return; } @@ -894,8 +934,8 @@ void QgsWmsProvider::readBlock( int bandNo, QgsRectangle const & viewExtent, in // If image is too large, ptr can be NULL memcpy( block, ptr, myExpectedSize ); } - // do not delete the image, it is handled by draw() - //delete image; + + delete image; } @@ -2895,8 +2935,6 @@ QString QgsWmsProvider::description() const void QgsWmsProvider::reloadData() { - delete mCachedImage; - mCachedImage = nullptr; } @@ -3376,14 +3414,15 @@ void QgsWmsImageDownloadHandler::cancelled() // ---------- -QgsWmsTiledImageDownloadHandler::QgsWmsTiledImageDownloadHandler( const QString& providerUri, const QgsWmsAuthorization& auth, int tileReqNo, const QList& requests, QImage* cachedImage, const QgsRectangle& cachedViewExtent, bool smoothPixmapTransform, QgsRasterBlockFeedback* feedback ) +QgsWmsTiledImageDownloadHandler::QgsWmsTiledImageDownloadHandler( const QString& providerUri, const QgsWmsAuthorization& auth, int tileReqNo, const QList& requests, QImage* image, const QgsRectangle& viewExtent, bool smoothPixmapTransform, QgsRasterBlockFeedback* feedback ) : mProviderUri( providerUri ) , mAuth( auth ) - , mCachedImage( cachedImage ) - , mCachedViewExtent( cachedViewExtent ) + , mImage( image ) + , mViewExtent( viewExtent ) , mEventLoop( new QEventLoop ) , mTileReqNo( tileReqNo ) , mSmoothPixmapTransform( smoothPixmapTransform ) + , mFeedback( feedback ) { Q_FOREACH ( const TileRequest& r, requests ) { @@ -3562,10 +3601,10 @@ void QgsWmsTiledImageDownloadHandler::tileReplyFinished() // only take results from current request number if ( mTileReqNo == tileReqNo ) { - double cr = mCachedViewExtent.width() / mCachedImage->width(); + double cr = mViewExtent.width() / mImage->width(); - QRectF dst(( r.left() - mCachedViewExtent.xMinimum() ) / cr, - ( mCachedViewExtent.yMaximum() - r.bottom() ) / cr, + QRectF dst(( r.left() - mViewExtent.xMinimum() ) / cr, + ( mViewExtent.yMaximum() - r.bottom() ) / cr, r.width() / cr, r.height() / cr ); @@ -3575,7 +3614,7 @@ void QgsWmsTiledImageDownloadHandler::tileReplyFinished() if ( !myLocalImage.isNull() ) { - QPainter p( mCachedImage ); + QPainter p( mImage ); if ( mSmoothPixmapTransform ) p.setRenderHint( QPainter::SmoothPixmapTransform, true ); p.drawImage( dst, myLocalImage ); @@ -3588,6 +3627,13 @@ void QgsWmsTiledImageDownloadHandler::tileReplyFinished() .arg( r.right() ).arg( r.top() ) .arg( r.width() ).arg( r.height() ) ); #endif + + sTileCacheMutex.lock(); + sTileCache.insert( reply->url(), new QImage( myLocalImage ) ); + sTileCacheMutex.unlock(); + + if ( mFeedback ) + mFeedback->onNewData(); } else { @@ -3611,13 +3657,16 @@ void QgsWmsTiledImageDownloadHandler::tileReplyFinished() } else { - // report any errors except for the one we have caused by cancelling the request - if ( reply->error() != QNetworkReply::OperationCanceledError ) + if ( !( mFeedback && mFeedback->preview_only ) ) { - QgsWmsStatistics::Stat& stat = QgsWmsStatistics::statForUri( mProviderUri ); - stat.errors++; + // report any errors except for the one we have caused by cancelling the request + if ( reply->error() != QNetworkReply::OperationCanceledError ) + { + QgsWmsStatistics::Stat& stat = QgsWmsStatistics::statForUri( mProviderUri ); + stat.errors++; - repeatTileRequest( reply->request() ); + repeatTileRequest( reply->request() ); + } } mReplies.removeOne( reply ); diff --git a/src/providers/wms/qgswmsprovider.h b/src/providers/wms/qgswmsprovider.h index b23ba3ec8a06..19fc12719f86 100644 --- a/src/providers/wms/qgswmsprovider.h +++ b/src/providers/wms/qgswmsprovider.h @@ -488,13 +488,6 @@ class QgsWmsProvider : public QgsRasterDataProvider */ QString mImageCrs; - /** - * The previously retrieved image from the WMS server. - * This can be reused if draw() is called consecutively - * with the same parameters. - */ - QImage *mCachedImage; - /** * The reply to the capabilities request */ @@ -510,13 +503,6 @@ class QgsWmsProvider : public QgsRasterDataProvider // TODO: better QString mIdentifyResultXsd; - /** - * The previous parameters to draw(). - */ - QgsRectangle mCachedViewExtent; - int mCachedViewWidth; - int mCachedViewHeight; - /** * The error caption associated with the last WMS error. */ @@ -603,7 +589,7 @@ class QgsWmsTiledImageDownloadHandler : public QObject int index; }; - QgsWmsTiledImageDownloadHandler( const QString& providerUri, const QgsWmsAuthorization& auth, int reqNo, const QList& requests, QImage* cachedImage, const QgsRectangle& cachedViewExtent, bool smoothPixmapTransform, QgsRasterBlockFeedback* feedback ); + QgsWmsTiledImageDownloadHandler( const QString& providerUri, const QgsWmsAuthorization& auth, int reqNo, const QList& requests, QImage* image, const QgsRectangle& viewExtent, bool smoothPixmapTransform, QgsRasterBlockFeedback* feedback ); ~QgsWmsTiledImageDownloadHandler(); void downloadBlocking(); @@ -628,8 +614,8 @@ class QgsWmsTiledImageDownloadHandler : public QObject QgsWmsAuthorization mAuth; - QImage* mCachedImage; - QgsRectangle mCachedViewExtent; + QImage* mImage; + QgsRectangle mViewExtent; QEventLoop* mEventLoop; @@ -638,6 +624,8 @@ class QgsWmsTiledImageDownloadHandler : public QObject //! Running tile requests QList mReplies; + + QgsRasterBlockFeedback* mFeedback; }; From e2bcba54d68eee196740e973a6869ab10e1c3785 Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Tue, 30 Aug 2016 12:04:59 +0800 Subject: [PATCH 02/26] WMTS preview - require a temporary image for updates of the raster Otherwise we would end up with rendering artifacts due to overpainting --- src/core/qgsmaprenderercustompainterjob.cpp | 6 ++++++ src/core/qgsmapsettings.h | 3 ++- src/core/qgsrendercontext.cpp | 1 + src/core/qgsrendercontext.h | 1 + src/core/raster/qgsrasterdrawer.cpp | 10 ++++++++++ src/core/raster/qgsrasterinterface.h | 5 ++++- src/core/raster/qgsrasterlayerrenderer.cpp | 7 +++++++ src/gui/qgsmapcanvas.cpp | 1 + 8 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/core/qgsmaprenderercustompainterjob.cpp b/src/core/qgsmaprenderercustompainterjob.cpp index 1b521d0209c1..c572e49a84db 100644 --- a/src/core/qgsmaprenderercustompainterjob.cpp +++ b/src/core/qgsmaprenderercustompainterjob.cpp @@ -333,6 +333,12 @@ bool QgsMapRendererJob::needTemporaryImage( QgsMapLayer* ml ) return true; } } + else if ( ml->type() == QgsMapLayer::RasterLayer ) + { + // preview of intermediate raster rendering results requires a temporary output image + if ( mSettings.testFlag( QgsMapSettings::RenderPartialOutput ) ) + return true; + } return false; } diff --git a/src/core/qgsmapsettings.h b/src/core/qgsmapsettings.h index 8fc3e60cc861..452c391ab2cd 100644 --- a/src/core/qgsmapsettings.h +++ b/src/core/qgsmapsettings.h @@ -164,7 +164,8 @@ class CORE_EXPORT QgsMapSettings UseRenderingOptimization = 0x20, //!< Enable vector simplification and other rendering optimizations DrawSelection = 0x40, //!< Whether vector selections should be shown in the rendered map DrawSymbolBounds = 0x80, //!< Draw bounds of symbols (for debugging/testing) - RenderMapTile = 0x100 //!< Draw map such that there are no problems between adjacent tiles + RenderMapTile = 0x100, //!< Draw map such that there are no problems between adjacent tiles + RenderPartialOutput = 0x200, //!< Whether to make extra effort to update map image with partially rendered layers (better for interactive map canvas). Added in QGIS 3.0 // TODO: ignore scale-based visibility (overview) }; Q_DECLARE_FLAGS( Flags, Flag ) diff --git a/src/core/qgsrendercontext.cpp b/src/core/qgsrendercontext.cpp index bd98369f873e..8a6957adc8e9 100644 --- a/src/core/qgsrendercontext.cpp +++ b/src/core/qgsrendercontext.cpp @@ -129,6 +129,7 @@ QgsRenderContext QgsRenderContext::fromMapSettings( const QgsMapSettings& mapSet ctx.setFlag( DrawSymbolBounds, mapSettings.testFlag( QgsMapSettings::DrawSymbolBounds ) ); ctx.setFlag( RenderMapTile, mapSettings.testFlag( QgsMapSettings::RenderMapTile ) ); ctx.setFlag( Antialiasing, mapSettings.testFlag( QgsMapSettings::Antialiasing ) ); + ctx.setFlag( RenderPartialOutput, mapSettings.testFlag( QgsMapSettings::RenderPartialOutput ) ); ctx.setRasterScaleFactor( 1.0 ); ctx.setScaleFactor( mapSettings.outputDpi() / 25.4 ); // = pixels per mm ctx.setRendererScale( mapSettings.scale() ); diff --git a/src/core/qgsrendercontext.h b/src/core/qgsrendercontext.h index c9102ce46cac..c3d91c236848 100644 --- a/src/core/qgsrendercontext.h +++ b/src/core/qgsrendercontext.h @@ -65,6 +65,7 @@ class CORE_EXPORT QgsRenderContext DrawSymbolBounds = 0x20, //!< Draw bounds of symbols (for debugging/testing) RenderMapTile = 0x40, //!< Draw map such that there are no problems between adjacent tiles Antialiasing = 0x80, //!< Use antialiasing while drawing + RenderPartialOutput = 0x100, //!< Whether to make extra effort to update map image with partially rendered layers (better for interactive map canvas). Added in QGIS 3.0 }; Q_DECLARE_FLAGS( Flags, Flag ) diff --git a/src/core/raster/qgsrasterdrawer.cpp b/src/core/raster/qgsrasterdrawer.cpp index 17df2ca815f0..06d32e5ca22f 100644 --- a/src/core/raster/qgsrasterdrawer.cpp +++ b/src/core/raster/qgsrasterdrawer.cpp @@ -88,10 +88,20 @@ void QgsRasterDrawer::draw( QPainter* p, QgsRasterViewPort* viewPort, const QgsM } } + if ( feedback && feedback->render_partial_output ) + { + // there could have been partial preview written before + // so overwrite anything with the resulting image. + // (we are guaranteed to have a temporary image for this layer, see QgsMapRendererJob::needTemporaryImage) + p->setCompositionMode( QPainter::CompositionMode_Source ); + } + drawImage( p, viewPort, img, topLeftCol, topLeftRow, theQgsMapToPixel ); delete block; + p->setCompositionMode( QPainter::CompositionMode_SourceOver ); // go back to the default composition mode + // ok this does not matter much anyway as the tile size quite big so most of the time // there would be just one tile for the whole display area, but it won't hurt... if ( feedback && feedback->isCancelled() ) diff --git a/src/core/raster/qgsrasterinterface.h b/src/core/raster/qgsrasterinterface.h index b0b53bec4bd0..5440fd25245f 100644 --- a/src/core/raster/qgsrasterinterface.h +++ b/src/core/raster/qgsrasterinterface.h @@ -37,12 +37,15 @@ class CORE_EXPORT QgsRasterBlockFeedback : public QgsFeedback { public: - QgsRasterBlockFeedback( QObject* parent = nullptr ) : QgsFeedback( parent ), preview_only( false ) {} + QgsRasterBlockFeedback( QObject* parent = nullptr ) : QgsFeedback( parent ), preview_only( false ), render_partial_output( false ) {} //! whether the raster provider should return only data that are already available //! without waiting for full result bool preview_only; + //! whether our painter is drawing to a temporary image used just by this layer + bool render_partial_output; + //! may be emitted by raster data provider to indicate that some partial data are available //! and a new preview image may be produced virtual void onNewData() {} diff --git a/src/core/raster/qgsrasterlayerrenderer.cpp b/src/core/raster/qgsrasterlayerrenderer.cpp index 3e72a787f901..33f6cbfd741a 100644 --- a/src/core/raster/qgsrasterlayerrenderer.cpp +++ b/src/core/raster/qgsrasterlayerrenderer.cpp @@ -231,22 +231,29 @@ MyFeedback::MyFeedback( QgsRasterLayerRenderer *r ) : mR( r ) , mMinimalPreviewInterval( 250 ) { + render_partial_output = r->mContext.testFlag( QgsRenderContext::RenderPartialOutput ); } void MyFeedback::onNewData() { qDebug( "\nGOT NEW DATA!\n" ); + if ( !render_partial_output ) + return; // we were not asked for partial renders and we may not have a temporary image for overwriting... + // update only once upon a time // (preview itself takes some time) if ( mLastPreview.msecsTo( QTime::currentTime() ) < mMinimalPreviewInterval ) return; + // TODO: update only the area that got new data + qDebug( "new raster preview! %d", mLastPreview.msecsTo( QTime::currentTime() ) ); QTime t; t.start(); QgsRasterBlockFeedback feedback; feedback.preview_only = true; + feedback.render_partial_output = true; QgsRasterIterator iterator( mR->mPipe->last() ); QgsRasterDrawer drawer( &iterator ); drawer.draw( mR->mPainter, mR->mRasterViewPort, mR->mMapToPixel, &feedback ); diff --git a/src/gui/qgsmapcanvas.cpp b/src/gui/qgsmapcanvas.cpp index 747670bbc984..d9071baa723d 100644 --- a/src/gui/qgsmapcanvas.cpp +++ b/src/gui/qgsmapcanvas.cpp @@ -143,6 +143,7 @@ QgsMapCanvas::QgsMapCanvas( QWidget * parent ) mSettings.setFlag( QgsMapSettings::DrawEditingInfo ); mSettings.setFlag( QgsMapSettings::UseRenderingOptimization ); + mSettings.setFlag( QgsMapSettings::RenderPartialOutput ); //segmentation parameters QSettings settings; From 0c51c3f08dbe0ea3a04fe23e5dd115e65335cc19 Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Wed, 31 Aug 2016 11:05:46 +0800 Subject: [PATCH 03/26] Housekeeping in WMS provider - move some calculations to utility functions --- src/providers/wms/qgswmscapabilities.cpp | 77 ++++++++++++++++++++- src/providers/wms/qgswmscapabilities.h | 29 ++++++-- src/providers/wms/qgswmsprovider.cpp | 87 +++++++----------------- 3 files changed, 123 insertions(+), 70 deletions(-) diff --git a/src/providers/wms/qgswmscapabilities.cpp b/src/providers/wms/qgswmscapabilities.cpp index 92dc72b8b299..783144fb8e9c 100644 --- a/src/providers/wms/qgswmscapabilities.cpp +++ b/src/providers/wms/qgswmscapabilities.cpp @@ -1258,6 +1258,7 @@ void QgsWmsCapabilities::parseTileSetProfile( QDomElement const &e ) m.matrixWidth = ceil( l.boundingBoxes.at( 0 ).box.width() / m.tileWidth / r ); m.matrixHeight = ceil( l.boundingBoxes.at( 0 ).box.height() / m.tileHeight / r ); m.topLeft = QgsPoint( l.boundingBoxes.at( 0 ).box.xMinimum(), l.boundingBoxes.at( 0 ).box.yMinimum() + m.matrixHeight * m.tileHeight * r ); + m.tres = r; ms.tileMatrices.insert( r, m ); i++; } @@ -1340,17 +1341,19 @@ void QgsWmsCapabilities::parseWMTSContents( QDomElement const &e ) m.matrixWidth = e1.firstChildElement( "MatrixWidth" ).text().toInt(); m.matrixHeight = e1.firstChildElement( "MatrixHeight" ).text().toInt(); - double res = m.scaleDenom * 0.00028 / metersPerUnit; + // the magic number below is "standardized rendering pixel size" defined + // in WMTS (and WMS 1.3) standard, being 0.28 pixel + m.tres = m.scaleDenom * 0.00028 / metersPerUnit; QgsDebugMsg( QString( " %1: scale=%2 res=%3 tile=%4x%5 matrix=%6x%7 topLeft=%8" ) .arg( m.identifier ) - .arg( m.scaleDenom ).arg( res ) + .arg( m.scaleDenom ).arg( m.tres ) .arg( m.tileWidth ).arg( m.tileHeight ) .arg( m.matrixWidth ).arg( m.matrixHeight ) .arg( m.topLeft.toString() ) ); - s.tileMatrices.insert( res, m ); + s.tileMatrices.insert( m.tres, m ); } mTileMatrixSets.insert( s.identifier, s ); @@ -1797,6 +1800,8 @@ bool QgsWmsCapabilities::detectTileLayerBoundingBox( QgsWmtsTileLayer& l ) const QgsWmtsTileMatrix& tm = *tmIt; double metersPerUnit = QgsUnitTypes::fromUnitToUnitFactor( crs.mapUnits(), QgsUnitTypes::DistanceMeters ); + // the magic number below is "standardized rendering pixel size" defined + // in WMTS (and WMS 1.3) standard, being 0.28 pixel double res = tm.scaleDenom * 0.00028 / metersPerUnit; QgsPoint bottomRight( tm.topLeft.x() + res * tm.tileWidth * tm.matrixWidth, tm.topLeft.y() - res * tm.tileHeight * tm.matrixHeight ); @@ -2049,3 +2054,69 @@ void QgsWmsCapabilitiesDownload::capabilitiesReplyFinished() emit downloadFinished(); } + +QRectF QgsWmtsTileMatrix::tileRect( int col, int row ) const +{ + double twMap = tileWidth * tres; + double thMap = tileHeight * tres; + return QRectF( topLeft.x() + col * twMap, topLeft.y() - ( row + 1 ) * thMap, twMap, thMap ); +} + +QgsRectangle QgsWmtsTileMatrix::tileBBox( int col, int row ) const +{ + double twMap = tileWidth * tres; + double thMap = tileHeight * tres; + return QgsRectangle( + topLeft.x() + col * twMap, + topLeft.y() - ( row + 1 ) * thMap, + topLeft.x() + ( col + 1 ) * twMap, + topLeft.y() - row * thMap ); +} + +void QgsWmtsTileMatrix::viewExtentIntersection( const QgsRectangle &viewExtent, const QgsWmtsTileMatrixLimits* tml, int &col0, int &row0, int &col1, int &row1 ) const +{ + double twMap = tileWidth * tres; + double thMap = tileHeight * tres; + + int minTileCol = 0; + int maxTileCol = matrixWidth - 1; + int minTileRow = 0; + int maxTileRow = matrixHeight - 1; + + if ( tml ) + { + minTileCol = tml->minTileCol; + maxTileCol = tml->maxTileCol; + minTileRow = tml->minTileRow; + maxTileRow = tml->maxTileRow; + //QgsDebugMsg( QString( "%1 %2: TileMatrixLimits col %3-%4 row %5-%6" ) + // .arg( tileMatrixSet->identifier, identifier ) + // .arg( minTileCol ).arg( maxTileCol ) + // .arg( minTileRow ).arg( maxTileRow ) ); + } + + col0 = qBound( minTileCol, ( int ) floor(( viewExtent.xMinimum() - topLeft.x() ) / twMap ), maxTileCol ); + row0 = qBound( minTileRow, ( int ) floor(( topLeft.y() - viewExtent.yMaximum() ) / thMap ), maxTileRow ); + col1 = qBound( minTileCol, ( int ) floor(( viewExtent.xMaximum() - topLeft.x() ) / twMap ), maxTileCol ); + row1 = qBound( minTileRow, ( int ) floor(( topLeft.y() - viewExtent.yMinimum() ) / thMap ), maxTileRow ); +} + +const QgsWmtsTileMatrix* QgsWmtsTileMatrixSet::findNearestResolution( double vres ) const +{ + QMap::const_iterator prev, it = tileMatrices.constBegin(); + while ( it != tileMatrices.constEnd() && it.key() < vres ) + { + //QgsDebugMsg( QString( "res:%1 >= %2" ).arg( it.key() ).arg( vres ) ); + prev = it; + ++it; + } + + if ( it == tileMatrices.constEnd() || + ( it != tileMatrices.constBegin() && vres - prev.key() < it.key() - vres ) ) + { + //QgsDebugMsg( "back to previous res" ); + it = prev; + } + + return &it.value(); +} diff --git a/src/providers/wms/qgswmscapabilities.h b/src/providers/wms/qgswmscapabilities.h index 47dbc0af4ea0..0fc3efde0c15 100644 --- a/src/providers/wms/qgswmscapabilities.h +++ b/src/providers/wms/qgswmscapabilities.h @@ -308,17 +308,33 @@ struct QgsWmtsTheme ~QgsWmtsTheme() { delete subTheme; } }; +struct QgsWmtsTileMatrixLimits; + struct QgsWmtsTileMatrix { QString identifier; QString title, abstract; QStringList keywords; double scaleDenom; - QgsPoint topLeft; - int tileWidth; - int tileHeight; - int matrixWidth; - int matrixHeight; + QgsPoint topLeft; //!< top-left corner of the tile matrix in map units + int tileWidth; //!< width of a tile in pixels + int tileHeight; //!< height of a tile in pixels + int matrixWidth; //!< number of tiles horizontally + int matrixHeight; //!< number of tiles vertically + double tres; //!< pixel span in map units + + //! Returns extent of a tile in map coordinates. + //! (same function as tileBBox() but returns QRectF instead of QgsRectangle) + QRectF tileRect( int col, int row ) const; + + //! Returns extent of a tile in map coordinates + //! (same function as tileRect() but returns QgsRectangle instead of QRectF) + QgsRectangle tileBBox( int col, int row ) const; + + //! Returns range of tiles that intersects with the view extent + //! (tml may be null) + void viewExtentIntersection( const QgsRectangle& viewExtent, const QgsWmtsTileMatrixLimits* tml, int& col0, int& row0, int& col1, int& row1 ) const; + }; struct QgsWmtsTileMatrixSet @@ -329,6 +345,9 @@ struct QgsWmtsTileMatrixSet QString crs; QString wkScaleSet; QMap tileMatrices; + + //! Returns closest tile resolution to the requested one. (resolution = width [map units] / with [pixels]) + const QgsWmtsTileMatrix* findNearestResolution( double vres ) const; }; enum QgsTileMode { WMTS, WMSC }; diff --git a/src/providers/wms/qgswmsprovider.cpp b/src/providers/wms/qgswmsprovider.cpp index f05f394ddfe9..353e5e38579f 100644 --- a/src/providers/wms/qgswmsprovider.cpp +++ b/src/providers/wms/qgswmsprovider.cpp @@ -566,9 +566,9 @@ QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, i mTileReqNo++; double vres = viewExtent.width() / pixelWidth; - double tres = vres; const QgsWmtsTileMatrix *tm = nullptr; + QScopedPointer tempTm; enum QgsTileMode tileMode; if ( mSettings.mTiled ) @@ -577,38 +577,22 @@ QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, i Q_ASSERT( mTileMatrixSet ); Q_ASSERT( !mTileMatrixSet->tileMatrices.isEmpty() ); - QMap &m = mTileMatrixSet->tileMatrices; - // find nearest resolution - QMap::const_iterator prev, it = m.constBegin(); - while ( it != m.constEnd() && it.key() < vres ) - { - QgsDebugMsg( QString( "res:%1 >= %2" ).arg( it.key() ).arg( vres ) ); - prev = it; - ++it; - } - - if ( it == m.constEnd() || - ( it != m.constBegin() && vres - prev.key() < it.key() - vres ) ) - { - QgsDebugMsg( "back to previous res" ); - it = prev; - } - - tres = it.key(); - tm = &it.value(); + tm = mTileMatrixSet->findNearestResolution( vres ); + Q_ASSERT( tm ); tileMode = mTileLayer->tileMode; } else if ( mSettings.mMaxWidth != 0 && mSettings.mMaxHeight != 0 ) { - static QgsWmtsTileMatrix tempTm; - tempTm.topLeft = QgsPoint( mLayerExtent.xMinimum(), mLayerExtent.yMaximum() ); - tempTm.tileWidth = mSettings.mMaxWidth; - tempTm.tileHeight = mSettings.mMaxHeight; - tempTm.matrixWidth = ceil( mLayerExtent.width() / mSettings.mMaxWidth / vres ); - tempTm.matrixHeight = ceil( mLayerExtent.height() / mSettings.mMaxHeight / vres ); - tm = &tempTm; + tempTm.reset( new QgsWmtsTileMatrix ); + tempTm->topLeft = QgsPoint( mLayerExtent.xMinimum(), mLayerExtent.yMaximum() ); + tempTm->tileWidth = mSettings.mMaxWidth; + tempTm->tileHeight = mSettings.mMaxHeight; + tempTm->matrixWidth = ceil( mLayerExtent.width() / mSettings.mMaxWidth / vres ); + tempTm->matrixHeight = ceil( mLayerExtent.height() / mSettings.mMaxHeight / vres ); + tempTm->tres = vres; + tm = tempTm.data(); tileMode = WMSC; } @@ -634,43 +618,24 @@ QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, i ); QgsDebugMsg( QString( "tile matrix %1,%2 res:%3 tilesize:%4x%5 matrixsize:%6x%7 id:%8" ) - .arg( tm->topLeft.x() ).arg( tm->topLeft.y() ).arg( tres ) + .arg( tm->topLeft.x() ).arg( tm->topLeft.y() ).arg( tm->tres ) .arg( tm->tileWidth ).arg( tm->tileHeight ) .arg( tm->matrixWidth ).arg( tm->matrixHeight ) .arg( tm->identifier ) ); - // calculate tile coordinates - double twMap = tm->tileWidth * tres; - double thMap = tm->tileHeight * tres; - QgsDebugMsg( QString( "tile map size: %1,%2" ).arg( qgsDoubleToString( twMap ), qgsDoubleToString( thMap ) ) ); - - int minTileCol = 0; - int maxTileCol = tm->matrixWidth - 1; - int minTileRow = 0; - int maxTileRow = tm->matrixHeight - 1; - + const QgsWmtsTileMatrixLimits *tml = nullptr; if ( mTileLayer && mTileLayer->setLinks.contains( mTileMatrixSet->identifier ) && mTileLayer->setLinks[ mTileMatrixSet->identifier ].limits.contains( tm->identifier ) ) { - const QgsWmtsTileMatrixLimits &tml = mTileLayer->setLinks[ mTileMatrixSet->identifier ].limits[ tm->identifier ]; - minTileCol = tml.minTileCol; - maxTileCol = tml.maxTileCol; - minTileRow = tml.minTileRow; - maxTileRow = tml.maxTileRow; - QgsDebugMsg( QString( "%1 %2: TileMatrixLimits col %3-%4 row %5-%6" ) - .arg( mTileMatrixSet->identifier, - tm->identifier ) - .arg( minTileCol ).arg( maxTileCol ) - .arg( minTileRow ).arg( maxTileRow ) ); + tml = &mTileLayer->setLinks[ mTileMatrixSet->identifier ].limits[ tm->identifier ]; } - int col0 = qBound( minTileCol, ( int ) floor(( viewExtent.xMinimum() - tm->topLeft.x() ) / twMap ), maxTileCol ); - int row0 = qBound( minTileRow, ( int ) floor(( tm->topLeft.y() - viewExtent.yMaximum() ) / thMap ), maxTileRow ); - int col1 = qBound( minTileCol, ( int ) floor(( viewExtent.xMaximum() - tm->topLeft.x() ) / twMap ), maxTileCol ); - int row1 = qBound( minTileRow, ( int ) floor(( tm->topLeft.y() - viewExtent.yMinimum() ) / thMap ), maxTileRow ); + // calculate tile coordinates + int col0, col1, row0, row1; + tm->viewExtentIntersection( viewExtent, tml, col0, row0, col1, row1 ); #if QGISDEBUG int n = ( col1 - col0 + 1 ) * ( row1 - row0 + 1 ); @@ -728,17 +693,17 @@ QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, i { for ( int col = col0; col <= col1; col++ ) { + QgsRectangle bbox( tm->tileBBox( col, row ) ); QString turl; turl += url.toString(); turl += QString( changeXY ? "&BBOX=%2,%1,%4,%3" : "&BBOX=%1,%2,%3,%4" ) - .arg( qgsDoubleToString( tm->topLeft.x() + col * twMap /* + twMap * 0.001 */ ), - qgsDoubleToString( tm->topLeft.y() - ( row + 1 ) * thMap /* - thMap * 0.001 */ ), - qgsDoubleToString( tm->topLeft.x() + ( col + 1 ) * twMap /* - twMap * 0.001 */ ), - qgsDoubleToString( tm->topLeft.y() - row * thMap /* + thMap * 0.001 */ ) ); + .arg( qgsDoubleToString( bbox.xMinimum() ), + qgsDoubleToString( bbox.yMinimum() ), + qgsDoubleToString( bbox.xMaximum() ), + qgsDoubleToString( bbox.yMaximum() ) ); QgsDebugMsg( QString( "tileRequest %1 %2/%3 (%4,%5): %6" ).arg( mTileReqNo ).arg( i++ ).arg( n ).arg( row ).arg( col ).arg( turl ) ); - QRectF rect( tm->topLeft.x() + col * twMap, tm->topLeft.y() - ( row + 1 ) * thMap, twMap, thMap ); - requests << QgsWmsTiledImageDownloadHandler::TileRequest( turl, rect, i ); + requests << QgsWmsTiledImageDownloadHandler::TileRequest( turl, tm->tileRect( col, row ), i ); } } } @@ -779,8 +744,7 @@ QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, i turl += QString( "&TILEROW=%1&TILECOL=%2" ).arg( row ).arg( col ); QgsDebugMsg( QString( "tileRequest %1 %2/%3 (%4,%5): %6" ).arg( mTileReqNo ).arg( i++ ).arg( n ).arg( row ).arg( col ).arg( turl ) ); - QRectF rect( tm->topLeft.x() + col * twMap, tm->topLeft.y() - ( row + 1 ) * thMap, twMap, thMap ); - requests << QgsWmsTiledImageDownloadHandler::TileRequest( turl, rect, i ); + requests << QgsWmsTiledImageDownloadHandler::TileRequest( turl, tm->tileRect( col, row ), i ); } } } @@ -810,8 +774,7 @@ QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, i if ( feedback && !feedback->preview_only ) QgsDebugMsg( QString( "tileRequest %1 %2/%3 (%4,%5): %6" ).arg( mTileReqNo ).arg( i++ ).arg( n ).arg( row ).arg( col ).arg( turl ) ); - QRectF rect( tm->topLeft.x() + col * twMap, tm->topLeft.y() - ( row + 1 ) * thMap, twMap, thMap ); - requests << QgsWmsTiledImageDownloadHandler::TileRequest( turl, rect, i ); + requests << QgsWmsTiledImageDownloadHandler::TileRequest( turl, tm->tileRect( col, row ), i ); } } } From c4181fa04d8be377eb34a4f54be9cf139e0aa558 Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Wed, 31 Aug 2016 15:32:13 +0800 Subject: [PATCH 04/26] [FEATURE] Native support for XYZ tile layers Yes, that means OpenStreetMap tiles and various other sources! No GUI so far... --- src/providers/wms/qgswmscapabilities.cpp | 31 +++++++ src/providers/wms/qgswmscapabilities.h | 41 ++++++--- src/providers/wms/qgswmsprovider.cpp | 107 +++++++++++++++++++++-- src/providers/wms/qgswmsprovider.h | 3 + 4 files changed, 161 insertions(+), 21 deletions(-) diff --git a/src/providers/wms/qgswmscapabilities.cpp b/src/providers/wms/qgswmscapabilities.cpp index 783144fb8e9c..a8bcaa55adb8 100644 --- a/src/providers/wms/qgswmscapabilities.cpp +++ b/src/providers/wms/qgswmscapabilities.cpp @@ -40,6 +40,37 @@ bool QgsWmsSettings::parseUri( const QString& uriString ) QgsDataSourceUri uri; uri.setEncodedUri( uriString ); + mXyz = false; // assume WMS / WMTS + + if ( uri.param( "type" ) == "xyz" ) + { + // for XYZ tiles most of the things do not apply + mTiled = true; + mXyz = true; + mTileDimensionValues.clear(); + mTileMatrixSetId = "tms0"; + mMaxWidth = 0; + mMaxHeight = 0; + mHttpUri = uri.param( "url" ); + mBaseUrl = mHttpUri; + mAuth.mUserName.clear(); + mAuth.mPassword.clear(); + mAuth.mReferer.clear(); + mAuth.mAuthCfg.clear(); + mIgnoreGetMapUrl = false; + mIgnoreGetFeatureInfoUrl = false; + mSmoothPixmapTransform = false; + mDpiMode = dpiNone; // does not matter what we set here + mActiveSubLayers = QStringList( "xyz" ); // just a placeholder to have one sub-layer + mActiveSubStyles = QStringList( "xyz" ); // just a placeholder to have one sub-style + mActiveSubLayerVisibility.clear(); + mFeatureCount = 0; + mImageMimeType.clear(); + mCrsId = "EPSG:3857"; + mEnableContextualLegend = false; + return true; + } + mTiled = false; mTileDimensionValues.clear(); diff --git a/src/providers/wms/qgswmscapabilities.h b/src/providers/wms/qgswmscapabilities.h index 0fc3efde0c15..4e4d078d58a7 100644 --- a/src/providers/wms/qgswmscapabilities.h +++ b/src/providers/wms/qgswmscapabilities.h @@ -339,18 +339,20 @@ struct QgsWmtsTileMatrix struct QgsWmtsTileMatrixSet { - QString identifier; - QString title, abstract; - QStringList keywords; - QString crs; - QString wkScaleSet; + QString identifier; //!< tile matrix set identifier + QString title; //!< human readable tile matrix set name + QString abstract; //!< brief description of the tile matrix set + QStringList keywords; //!< list of words/phrases to describe the dataset + QString crs; //!< CRS of the tile matrix set + QString wkScaleSet; //!< optional reference to a well-known scale set + //! available tile matrixes (key = pixel span in map units) QMap tileMatrices; //! Returns closest tile resolution to the requested one. (resolution = width [map units] / with [pixels]) const QgsWmtsTileMatrix* findNearestResolution( double vres ) const; }; -enum QgsTileMode { WMTS, WMSC }; +enum QgsTileMode { WMTS, WMSC, XYZ }; struct QgsWmtsTileMatrixLimits { @@ -382,16 +384,22 @@ struct QgsWmtsStyle QList legendURLs; }; +/** + * In case of multi-dimensional data, the service metadata can describe their multi- + * dimensionality and tiles can be requested at specific values in these dimensions. + * Examples of dimensions are Time, Elevation and Band. + */ struct QgsWmtsDimension { - QString identifier; - QString title, abstract; - QStringList keywords; - QString UOM; - QString unitSymbol; - QString defaultValue; - bool current; - QStringList values; + QString identifier; //!< name of the dimensional axis + QString title; //!< human readable name + QString abstract; //!< brief description of the dimension + QStringList keywords; //!< list of words/phrases to describe the dataset + QString UOM; //!< units of measure of dimensional axis + QString unitSymbol; //!< symbol of the units + QString defaultValue; //!< default value to be used if value is not specified in request + bool current; //!< indicates whether temporal data are normally kept current + QStringList values; //!< available values for this dimension }; struct QgsWmtsTileLayer @@ -404,6 +412,7 @@ struct QgsWmtsTileLayer QStringList formats; QStringList infoFormats; QString defaultStyle; + //! available dimensions (optional, for multi-dimensional data) QHash dimensions; QHash styles; QHash setLinks; @@ -532,7 +541,11 @@ class QgsWmsSettings //! layer is tiled, tile layer and active matrix set bool mTiled; + //! whether we actually work with XYZ tiles instead of WMS / WMTS + bool mXyz; + //! chosen values for dimensions in case of multi-dimensional data (key=dim id, value=dim value) QHash mTileDimensionValues; + //! name of the chosen tile matrix set QString mTileMatrixSetId; /** diff --git a/src/providers/wms/qgswmsprovider.cpp b/src/providers/wms/qgswmsprovider.cpp index 353e5e38579f..941a5f46c9cd 100644 --- a/src/providers/wms/qgswmsprovider.cpp +++ b/src/providers/wms/qgswmsprovider.cpp @@ -79,6 +79,7 @@ static QString DEFAULT_LATLON_CRS = "CRS:84"; QMap QgsWmsStatistics::sData; + QgsWmsProvider::QgsWmsProvider( QString const& uri, const QgsWmsCapabilities* capabilities ) : QgsRasterDataProvider( uri ) , mHttpGetLegendGraphicResponse( nullptr ) @@ -110,14 +111,26 @@ QgsWmsProvider::QgsWmsProvider( QString const& uri, const QgsWmsCapabilities* ca if ( !addLayers() ) return; - // if there are already parsed capabilities, use them! - if ( capabilities ) - mCaps = *capabilities; - - // Make sure we have capabilities - other functions here may need them - if ( !retrieveServerCapabilities() ) + if ( mSettings.mXyz ) { - return; + // we are working with XYZ tiles + // no need to get capabilities, the whole definition is in URI + // so we just generate a dummy WMTS definition + setupXyzCapabilities( uri ); + } + else + { + // we are working with WMS / WMTS server + + // if there are already parsed capabilities, use them! + if ( capabilities ) + mCaps = *capabilities; + + // Make sure we have capabilities - other functions here may need them + if ( !retrieveServerCapabilities() ) + { + return; + } } // setImageCrs is using mTiled !!! @@ -781,6 +794,28 @@ QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, i } break; + case XYZ: + { + QString url = mSettings.mBaseUrl; + int z = tm->identifier.toInt(); + int i = 0; + for ( int row = row0; row <= row1; row++ ) + { + for ( int col = col0; col <= col1; col++ ) + { + QString turl( url ); + turl.replace( "{x}", QString::number( col ), Qt::CaseInsensitive ); + turl.replace( "{y}", QString::number( row ), Qt::CaseInsensitive ); + turl.replace( "{z}", QString::number( z ), Qt::CaseInsensitive ); + + if ( feedback && !feedback->preview_only ) + QgsDebugMsg( QString( "tileRequest %1 %2/%3 (%4,%5): %6" ).arg( mTileReqNo ).arg( i++ ).arg( n ).arg( row ).arg( col ).arg( turl ) ); + requests << QgsWmsTiledImageDownloadHandler::TileRequest( turl, tm->tileRect( col, row ), i ); + } + } + } + break; + default: QgsDebugMsg( QString( "unexpected tile mode %1" ).arg( mTileLayer->tileMode ) ); return image; @@ -935,6 +970,60 @@ bool QgsWmsProvider::retrieveServerCapabilities( bool forceRefresh ) } +void QgsWmsProvider::setupXyzCapabilities( const QString &uri ) +{ + QgsDataSourceUri parsedUri; + parsedUri.setEncodedUri( uri ); + + QgsCoordinateTransform ct( QgsCoordinateReferenceSystem( "EPSG:4326" ), QgsCoordinateReferenceSystem( mSettings.mCrsId ) ); + // the whole world is projected to a square: + // X going from 180 W to 180 E + // Y going from ~85 N to ~85 S (=atan(sinh(pi)) ... to get a square) + QgsPoint topLeftLonLat( -180, 180.0 / M_PI * atan( sinh( M_PI ) ) ); + QgsPoint bottomRightLonLat( 180, 180.0 / M_PI * atan( sinh( -M_PI ) ) ); + QgsPoint topLeft = ct.transform( topLeftLonLat ); + QgsPoint bottomRight = ct.transform( bottomRightLonLat ); + double xspan = ( bottomRight.x() - topLeft.x() ); + + QgsWmsBoundingBoxProperty bbox; + bbox.crs = mSettings.mCrsId; + bbox.box = QgsRectangle( topLeft.x(), bottomRight.y(), bottomRight.x(), topLeft.y() ); + + QgsWmtsTileLayer tl; + tl.tileMode = XYZ; + tl.identifier = "xyz"; // as set in parseUri + tl.boundingBoxes << bbox; + mCaps.mTileLayersSupported.append( tl ); + + QgsWmtsTileMatrixSet tms; + tms.identifier = "tms0"; // as set in parseUri + tms.crs = mSettings.mCrsId; + mCaps.mTileMatrixSets[tms.identifier] = tms; + + int minZoom = 0; + int maxZoom = 18; + if ( parsedUri.hasParam( "zmin" ) ) + minZoom = parsedUri.param( "zmin" ).toInt(); + if ( parsedUri.hasParam( "zmax" ) ) + maxZoom = parsedUri.param( "zmax" ).toInt(); + + // zoom 0 is one tile for the whole world + for ( int zoom = minZoom; zoom <= maxZoom; ++zoom ) + { + QgsWmtsTileMatrix tm; + tm.identifier = QString::number( zoom ); + tm.topLeft = topLeft; + tm.tileWidth = 256; + tm.tileHeight = 256; + tm.matrixWidth = pow( 2, zoom ); + tm.matrixHeight = pow( 2, zoom ); + tm.tres = xspan / ( tm.tileWidth * tm.matrixWidth ); + + mCaps.mTileMatrixSets[tms.identifier].tileMatrices[tm.tres] = tm; + } +} + + Qgis::DataType QgsWmsProvider::dataType( int bandNo ) const { return sourceDataType( bandNo ); @@ -1835,6 +1924,10 @@ QString QgsWmsProvider::metadata() { metadata += tr( "WMS-C" ); } + else if ( l.tileMode == XYZ ) + { + metadata += tr( "XYZ" ); + } else { metadata += tr( "Invalid tile mode" ); diff --git a/src/providers/wms/qgswmsprovider.h b/src/providers/wms/qgswmsprovider.h index 19fc12719f86..feb48d802c7b 100644 --- a/src/providers/wms/qgswmsprovider.h +++ b/src/providers/wms/qgswmsprovider.h @@ -363,6 +363,9 @@ class QgsWmsProvider : public QgsRasterDataProvider private: + //! In case of XYZ tile layer, setup capabilities from its URI + void setupXyzCapabilities( const QString& uri ); + QImage *draw( QgsRectangle const & viewExtent, int pixelWidth, int pixelHeight, QgsRasterBlockFeedback* feedback ); /** From 4a1b4fd52e77ec3423d17b2cc53def91b9cbb8b9 Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Wed, 31 Aug 2016 16:37:05 +0800 Subject: [PATCH 05/26] GUI for XYZ tile layers: browser items + actions to add/remove them --- src/providers/wms/CMakeLists.txt | 1 + src/providers/wms/qgswmsdataitems.cpp | 86 ++++++++++++++++++++++++++ src/providers/wms/qgswmsdataitems.h | 47 ++++++++++++++ src/providers/wms/qgsxyzconnection.cpp | 59 ++++++++++++++++++ src/providers/wms/qgsxyzconnection.h | 47 ++++++++++++++ 5 files changed, 240 insertions(+) create mode 100644 src/providers/wms/qgsxyzconnection.cpp create mode 100644 src/providers/wms/qgsxyzconnection.h diff --git a/src/providers/wms/CMakeLists.txt b/src/providers/wms/CMakeLists.txt index bc19de46db6b..f4ec13c4d978 100644 --- a/src/providers/wms/CMakeLists.txt +++ b/src/providers/wms/CMakeLists.txt @@ -11,6 +11,7 @@ SET (WMS_SRCS qgswmsdataitems.cpp qgstilescalewidget.cpp qgswmtsdimensions.cpp + qgsxyzconnection.cpp ) SET (WMS_MOC_HDRS qgswmscapabilities.h diff --git a/src/providers/wms/qgswmsdataitems.cpp b/src/providers/wms/qgswmsdataitems.cpp index 0f4f7651256d..a0a1e2860b3b 100644 --- a/src/providers/wms/qgswmsdataitems.cpp +++ b/src/providers/wms/qgswmsdataitems.cpp @@ -16,12 +16,16 @@ #include "qgslogger.h" +#include "qgsdataitemproviderregistry.h" #include "qgsdatasourceuri.h" #include "qgswmscapabilities.h" #include "qgswmsconnection.h" #include "qgswmssourceselect.h" #include "qgsnewhttpconnection.h" #include "qgstilescalewidget.h" +#include "qgsxyzconnection.h" + +#include // --------------------------------------------------------------------------- QgsWMSConnectionItem::QgsWMSConnectionItem( QgsDataItem* parent, QString name, QString path, QString uri ) @@ -416,6 +420,10 @@ void QgsWMSRootItem::newConnection() QGISEXTERN void registerGui( QMainWindow *mainWindow ) { QgsTileScaleWidget::showTileScale( mainWindow ); + + // with dataItem(...) at provider level we can only have one root item, + // so we have a data item provider for XYZ tile layers + QgsDataItemProviderRegistry::instance()->addProvider( new QgsXyzTileDataItemProvider ); } QGISEXTERN QgsWMSSourceSelect * selectWidget( QWidget * parent, Qt::WindowFlags fl ) @@ -450,3 +458,81 @@ QGISEXTERN QgsDataItem * dataItem( QString thePath, QgsDataItem* parentItem ) return nullptr; } + +// --------------------------------------------------------------------------- + + +QgsXyzTileRootItem::QgsXyzTileRootItem( QgsDataItem *parent, QString name, QString path ) + : QgsDataCollectionItem( parent, name, path ) +{ + mCapabilities |= Fast; + mIconName = "mIconWms.svg"; + populate(); +} + +QVector QgsXyzTileRootItem::createChildren() +{ + QVector connections; + Q_FOREACH ( const QString& connName, QgsXyzConnectionUtils::connectionList() ) + { + QgsXyzConnection connection( QgsXyzConnectionUtils::connection( connName ) ); + QgsDataItem * conn = new QgsXyzLayerItem( this, connName, mPath + '/' + connName, connection.encodedUri() ); + connections.append( conn ); + } + return connections; +} + +QList QgsXyzTileRootItem::actions() +{ + QAction* actionNew = new QAction( tr( "New Connection..." ), this ); + connect( actionNew, SIGNAL( triggered() ), this, SLOT( newConnection() ) ); + return QList() << actionNew; +} + +void QgsXyzTileRootItem::newConnection() +{ + QString url = QInputDialog::getText( nullptr, tr( "New XYZ tile layer" ), + tr( "Please enter XYZ tile layer URL. {x}, {y}, {z} will be replaced by actual tile coordinates." ) ); + if ( url.isEmpty() ) + return; + + QString name = QInputDialog::getText( nullptr, tr( "New XYZ tile layer" ), + tr( "Please enter name of the tile layer:" ) ); + if ( name.isEmpty() ) + return; + + QgsXyzConnection conn; + conn.name = name; + conn.url = url; + QgsXyzConnectionUtils::addConnection( conn ); + + refresh(); +} + + +// --------------------------------------------------------------------------- + + +QgsXyzLayerItem::QgsXyzLayerItem( QgsDataItem *parent, QString name, QString path, const QString &encodedUri ) + : QgsLayerItem( parent, name, path, encodedUri, QgsLayerItem::Raster, "wms" ) +{ + setState( Populated ); +} + +QList QgsXyzLayerItem::actions() +{ + QList lst = QgsLayerItem::actions(); + + QAction* actionDelete = new QAction( tr( "Delete" ), this ); + connect( actionDelete, SIGNAL( triggered() ), this, SLOT( deleteConnection() ) ); + lst << actionDelete; + + return lst; +} + +void QgsXyzLayerItem::deleteConnection() +{ + QgsXyzConnectionUtils::deleteConnection( mName ); + + mParent->refresh(); +} diff --git a/src/providers/wms/qgswmsdataitems.h b/src/providers/wms/qgswmsdataitems.h index 5df903dce7da..ca7fa3683191 100644 --- a/src/providers/wms/qgswmsdataitems.h +++ b/src/providers/wms/qgswmsdataitems.h @@ -16,6 +16,7 @@ #define QGSWMSDATAITEMS_H #include "qgsdataitem.h" +#include "qgsdataitemprovider.h" #include "qgsdatasourceuri.h" #include "qgswmsprovider.h" @@ -105,4 +106,50 @@ class QgsWMSRootItem : public QgsDataCollectionItem void newConnection(); }; +//! Root item for XYZ tile layers +class QgsXyzTileRootItem : public QgsDataCollectionItem +{ + Q_OBJECT + public: + QgsXyzTileRootItem( QgsDataItem* parent, QString name, QString path ); + + QVector createChildren() override; + + virtual QList actions() override; + + private slots: + void newConnection(); +}; + +//! Item implementation for XYZ tile layers +class QgsXyzLayerItem : public QgsLayerItem +{ + Q_OBJECT + public: + QgsXyzLayerItem( QgsDataItem* parent, QString name, QString path, const QString& encodedUri ); + + virtual QList actions() override; + + public slots: + void deleteConnection(); +}; + + +//! Provider for XYZ root data item +class QgsXyzTileDataItemProvider : public QgsDataItemProvider +{ + public: + virtual QString name() override { return "XYZ Tiles"; } + + virtual int capabilities() override { return QgsDataProvider::Net; } + + virtual QgsDataItem* createDataItem( const QString& path, QgsDataItem* parentItem ) override + { + if ( path.isEmpty() ) + return new QgsXyzTileRootItem( parentItem, "Tile Server (XYZ)", "xyz:" ); + return nullptr; + } +}; + + #endif // QGSWMSDATAITEMS_H diff --git a/src/providers/wms/qgsxyzconnection.cpp b/src/providers/wms/qgsxyzconnection.cpp new file mode 100644 index 000000000000..28a377343137 --- /dev/null +++ b/src/providers/wms/qgsxyzconnection.cpp @@ -0,0 +1,59 @@ +/*************************************************************************** + qgsxyzconnection.h + --------------------- + begin : August 2016 + copyright : (C) 2016 by Martin Dobias + email : wonder dot sk at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsxyzconnection.h" + +#include "qgsdatasourceuri.h" + +#include + +QString QgsXyzConnection::encodedUri() const +{ + QgsDataSourceUri uri; + uri.setParam( "type", "xyz" ); + uri.setParam( "url", url ); + return uri.encodedUri(); +} + +QStringList QgsXyzConnectionUtils::connectionList() +{ + QSettings settings; + settings.beginGroup( "/Qgis/connections-xyz" ); + return settings.childGroups(); +} + +QgsXyzConnection QgsXyzConnectionUtils::connection( const QString &name ) +{ + QSettings settings; + settings.beginGroup( "/Qgis/connections-xyz/" + name ); + + QgsXyzConnection conn; + conn.name = name; + conn.url = settings.value( "url" ).toString(); + return conn; +} + +void QgsXyzConnectionUtils::deleteConnection( const QString& name ) +{ + QSettings settings; + settings.remove( "/Qgis/connections-xyz/" + name ); +} + +void QgsXyzConnectionUtils::addConnection( const QgsXyzConnection &conn ) +{ + QSettings settings; + settings.beginGroup( "/Qgis/connections-xyz/" + conn.name ); + settings.setValue( "url", conn.url ); +} diff --git a/src/providers/wms/qgsxyzconnection.h b/src/providers/wms/qgsxyzconnection.h new file mode 100644 index 000000000000..ba1882546f60 --- /dev/null +++ b/src/providers/wms/qgsxyzconnection.h @@ -0,0 +1,47 @@ +/*************************************************************************** + qgsxyzconnection.h + --------------------- + begin : August 2016 + copyright : (C) 2016 by Martin Dobias + email : wonder dot sk at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSXYZCONNECTION_H +#define QGSXYZCONNECTION_H + +#include + +struct QgsXyzConnection +{ + QString name; + QString url; + + QString encodedUri() const; +}; + +/** Utility class for handling list of connections to XYZ tile layers */ +class QgsXyzConnectionUtils +{ + public: + //! Returns list of existing connections + static QStringList connectionList(); + + //! Returns connection details + static QgsXyzConnection connection( const QString& name ); + + //! Removes a connection from the list + static void deleteConnection( const QString& name ); + + //! Adds a new connection to the list + static void addConnection( const QgsXyzConnection& conn ); +}; + + +#endif // QGSXYZCONNECTION_H From b919198e465e3556425bd51758e22b7407bf1e0e Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Wed, 31 Aug 2016 18:37:46 +0800 Subject: [PATCH 06/26] Fix loading of XYZ tile layer data item provider --- src/core/qgsdataitemproviderregistry.cpp | 13 +++++++++++++ src/providers/wms/qgswmsdataitems.cpp | 16 +++++++--------- src/providers/wms/qgswmsdataitems.h | 13 +++++++++++++ 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/src/core/qgsdataitemproviderregistry.cpp b/src/core/qgsdataitemproviderregistry.cpp index 8e7e51645513..8608cd1c987b 100644 --- a/src/core/qgsdataitemproviderregistry.cpp +++ b/src/core/qgsdataitemproviderregistry.cpp @@ -21,6 +21,8 @@ #include "qgslogger.h" #include "qgsproviderregistry.h" +typedef QList dataItemProviders_t(); + /** * \ingroup core @@ -64,6 +66,17 @@ QgsDataItemProviderRegistry::QgsDataItemProviderRegistry() if ( !library ) continue; + // new / better way of returning data items from providers + + dataItemProviders_t* dataItemProvidersFn = reinterpret_cast< dataItemProviders_t * >( cast_to_fptr( library->resolve( "dataItemProviders" ) ) ); + if ( dataItemProvidersFn ) + { + // the function is a factory - we keep ownership of the returned providers + mProviders << dataItemProvidersFn(); + } + + // legacy support - using dataItem() and dataCapabilities() methods + dataCapabilities_t * dataCapabilities = reinterpret_cast< dataCapabilities_t * >( cast_to_fptr( library->resolve( "dataCapabilities" ) ) ); if ( !dataCapabilities ) { diff --git a/src/providers/wms/qgswmsdataitems.cpp b/src/providers/wms/qgswmsdataitems.cpp index a0a1e2860b3b..e28ac7a8859f 100644 --- a/src/providers/wms/qgswmsdataitems.cpp +++ b/src/providers/wms/qgswmsdataitems.cpp @@ -420,10 +420,6 @@ void QgsWMSRootItem::newConnection() QGISEXTERN void registerGui( QMainWindow *mainWindow ) { QgsTileScaleWidget::showTileScale( mainWindow ); - - // with dataItem(...) at provider level we can only have one root item, - // so we have a data item provider for XYZ tile layers - QgsDataItemProviderRegistry::instance()->addProvider( new QgsXyzTileDataItemProvider ); } QGISEXTERN QgsWMSSourceSelect * selectWidget( QWidget * parent, Qt::WindowFlags fl ) @@ -431,12 +427,8 @@ QGISEXTERN QgsWMSSourceSelect * selectWidget( QWidget * parent, Qt::WindowFlags return new QgsWMSSourceSelect( parent, fl ); } -QGISEXTERN int dataCapabilities() -{ - return QgsDataProvider::Net; -} -QGISEXTERN QgsDataItem * dataItem( QString thePath, QgsDataItem* parentItem ) +QgsDataItem* QgsWmsDataItemProvider::createDataItem( const QString& thePath, QgsDataItem *parentItem ) { QgsDebugMsg( "thePath = " + thePath ); if ( thePath.isEmpty() ) @@ -458,6 +450,12 @@ QGISEXTERN QgsDataItem * dataItem( QString thePath, QgsDataItem* parentItem ) return nullptr; } +QGISEXTERN QList dataItemProviders() +{ + return QList() + << new QgsWmsDataItemProvider + << new QgsXyzTileDataItemProvider; +} // --------------------------------------------------------------------------- diff --git a/src/providers/wms/qgswmsdataitems.h b/src/providers/wms/qgswmsdataitems.h index ca7fa3683191..d6dc4b8f7bbf 100644 --- a/src/providers/wms/qgswmsdataitems.h +++ b/src/providers/wms/qgswmsdataitems.h @@ -106,6 +106,19 @@ class QgsWMSRootItem : public QgsDataCollectionItem void newConnection(); }; + +//! Provider for WMS root data item +class QgsWmsDataItemProvider : public QgsDataItemProvider +{ + public: + virtual QString name() override { return "WMS"; } + + virtual int capabilities() override { return QgsDataProvider::Net; } + + virtual QgsDataItem* createDataItem( const QString& path, QgsDataItem* parentItem ) override; +}; + + //! Root item for XYZ tile layers class QgsXyzTileRootItem : public QgsDataCollectionItem { From a5039d6d73efc582640083b6cf542f035e5e86d2 Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Thu, 1 Sep 2016 10:15:44 +0800 Subject: [PATCH 07/26] Order tile requests according to their distance from view center ... so we first receive tiles in the middle rather those at the border --- src/providers/wms/qgswmsprovider.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/providers/wms/qgswmsprovider.cpp b/src/providers/wms/qgswmsprovider.cpp index 941a5f46c9cd..35c4665a6350 100644 --- a/src/providers/wms/qgswmsprovider.cpp +++ b/src/providers/wms/qgswmsprovider.cpp @@ -79,6 +79,18 @@ static QString DEFAULT_LATLON_CRS = "CRS:84"; QMap QgsWmsStatistics::sData; +//! a helper class for ordering tile requests according to the distance from view center +struct LessThanTileRequest +{ + QgsPoint center; + bool operator()( const QgsWmsTiledImageDownloadHandler::TileRequest &req1, const QgsWmsTiledImageDownloadHandler::TileRequest &req2 ) + { + QPointF p1 = req1.rect.center(); + QPointF p2 = req2.rect.center(); + return center.distance( p1.x(), p1.y() ) < center.distance( p2.x(), p2.y() ); + } +}; + QgsWmsProvider::QgsWmsProvider( QString const& uri, const QgsWmsCapabilities* capabilities ) : QgsRasterDataProvider( uri ) @@ -887,6 +899,11 @@ QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, i if ( feedback && memCached + diskCached > 0 ) feedback->onNewData(); + // order tile requests according to the distance from view center + LessThanTileRequest cmp; + cmp.center = viewExtent.center(); + qSort( requestsFinal.begin(), requestsFinal.end(), cmp ); + QgsWmsTiledImageDownloadHandler handler( dataSourceUri(), mSettings.authorization(), mTileReqNo, requestsFinal, image, viewExtent, mSettings.mSmoothPixmapTransform, feedback ); handler.downloadBlocking(); } From 6bb291f2d656d1f0acbcf3a463c795adfb217c8b Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Thu, 1 Sep 2016 15:38:58 +0800 Subject: [PATCH 08/26] Raster projector constructor cleanup Marked raster interface as uncopiable (it should have always been uncopiable) --- python/core/raster/qgsrasterinterface.sip | 5 + python/core/raster/qgsrasterprojector.sip | 35 +---- src/core/raster/qgsrasterinterface.h | 3 + src/core/raster/qgsrasterprojector.cpp | 148 ++-------------------- src/core/raster/qgsrasterprojector.h | 63 ++------- 5 files changed, 38 insertions(+), 216 deletions(-) diff --git a/python/core/raster/qgsrasterinterface.sip b/python/core/raster/qgsrasterinterface.sip index b8137c19e600..b51a01336c8e 100644 --- a/python/core/raster/qgsrasterinterface.sip +++ b/python/core/raster/qgsrasterinterface.sip @@ -256,5 +256,10 @@ class QgsRasterInterface int theStats = QgsRasterBandStats::All, const QgsRectangle & theExtent = QgsRectangle(), int theBinCount = 0 ); + + private: + QgsRasterInterface(const QgsRasterInterface &); + QgsRasterInterface &operator=(const QgsRasterInterface &); + }; diff --git a/python/core/raster/qgsrasterprojector.sip b/python/core/raster/qgsrasterprojector.sip index 33e129e033ec..813eadfbbf52 100644 --- a/python/core/raster/qgsrasterprojector.sip +++ b/python/core/raster/qgsrasterprojector.sip @@ -1,6 +1,10 @@ -/** Raster projector */ - +/** \ingroup core + * \brief QgsRasterProjector implements approximate projection support for + * it calculates grid of points in source CRS for target CRS + extent + * which are used to calculate affine transformation matrices. + * \class QgsRasterProjector + */ class QgsRasterProjector : QgsRasterInterface { %TypeHeaderCode @@ -18,33 +22,6 @@ class QgsRasterProjector : QgsRasterInterface Exact, //!< Exact, precise but slow }; - /** \brief QgsRasterProjector implements approximate projection support for - * it calculates grid of points in source CRS for target CRS + extent - * which are used to calculate affine transformation matrices. - */ - - QgsRasterProjector( const QgsCoordinateReferenceSystem& theSrcCRS, - const QgsCoordinateReferenceSystem& theDestCRS, - int theSrcDatumTransform, - int theDestDatumTransform, - const QgsRectangle& theDestExtent, - int theDestRows, int theDestCols, - double theMaxSrcXRes, double theMaxSrcYRes, - const QgsRectangle& theExtent - ); - - QgsRasterProjector( const QgsCoordinateReferenceSystem& theSrcCRS, - const QgsCoordinateReferenceSystem& theDestCRS, - const QgsRectangle& theDestExtent, - int theDestRows, int theDestCols, - double theMaxSrcXRes, double theMaxSrcYRes, - const QgsRectangle& theExtent - ); - QgsRasterProjector( const QgsCoordinateReferenceSystem& theSrcCRS, - const QgsCoordinateReferenceSystem& theDestCRS, - double theMaxSrcXRes, double theMaxSrcYRes, - const QgsRectangle& theExtent - ); QgsRasterProjector(); /** \brief The destructor */ diff --git a/src/core/raster/qgsrasterinterface.h b/src/core/raster/qgsrasterinterface.h index 5440fd25245f..cdc8805022ab 100644 --- a/src/core/raster/qgsrasterinterface.h +++ b/src/core/raster/qgsrasterinterface.h @@ -270,6 +270,9 @@ class CORE_EXPORT QgsRasterInterface int theStats = QgsRasterBandStats::All, const QgsRectangle & theExtent = QgsRectangle(), int theBinCount = 0 ); + + private: + Q_DISABLE_COPY( QgsRasterInterface ) // there is clone() for copying }; #endif diff --git a/src/core/raster/qgsrasterprojector.cpp b/src/core/raster/qgsrasterprojector.cpp index 82c9cf80a5be..626333b6f8d7 100644 --- a/src/core/raster/qgsrasterprojector.cpp +++ b/src/core/raster/qgsrasterprojector.cpp @@ -23,98 +23,14 @@ #include "qgscoordinatetransform.h" #include "qgscsexception.h" -QgsRasterProjector::QgsRasterProjector( - const QgsCoordinateReferenceSystem& theSrcCRS, - const QgsCoordinateReferenceSystem& theDestCRS, - int theSrcDatumTransform, - int theDestDatumTransform, - const QgsRectangle& theDestExtent, - int theDestRows, int theDestCols, - double theMaxSrcXRes, double theMaxSrcYRes, - const QgsRectangle& theExtent ) - : QgsRasterInterface( nullptr ) - , mSrcCRS( theSrcCRS ) - , mDestCRS( theDestCRS ) - , mSrcDatumTransform( theSrcDatumTransform ) - , mDestDatumTransform( theDestDatumTransform ) - , mDestExtent( theDestExtent ) - , mExtent( theExtent ) - , mDestRows( theDestRows ), mDestCols( theDestCols ) - , pHelperTop( nullptr ), pHelperBottom( nullptr ) - , mMaxSrcXRes( theMaxSrcXRes ), mMaxSrcYRes( theMaxSrcYRes ) - , mPrecision( Approximate ) - , mApproximate( true ) -{ - QgsDebugMsgLevel( "Entered", 4 ); - QgsDebugMsgLevel( "theDestExtent = " + theDestExtent.toString(), 4 ); - - calc(); -} - -QgsRasterProjector::QgsRasterProjector( - const QgsCoordinateReferenceSystem& theSrcCRS, - const QgsCoordinateReferenceSystem& theDestCRS, - const QgsRectangle& theDestExtent, - int theDestRows, int theDestCols, - double theMaxSrcXRes, double theMaxSrcYRes, - const QgsRectangle& theExtent ) - : QgsRasterInterface( nullptr ) - , mSrcCRS( theSrcCRS ) - , mDestCRS( theDestCRS ) - , mSrcDatumTransform( -1 ) - , mDestDatumTransform( -1 ) - , mDestExtent( theDestExtent ) - , mExtent( theExtent ) - , mDestRows( theDestRows ), mDestCols( theDestCols ) - , pHelperTop( nullptr ), pHelperBottom( nullptr ) - , mMaxSrcXRes( theMaxSrcXRes ), mMaxSrcYRes( theMaxSrcYRes ) - , mPrecision( Approximate ) - , mApproximate( false ) -{ - QgsDebugMsgLevel( "Entered", 4 ); - QgsDebugMsgLevel( "theDestExtent = " + theDestExtent.toString(), 4 ); - calc(); -} -QgsRasterProjector::QgsRasterProjector( - const QgsCoordinateReferenceSystem& theSrcCRS, - const QgsCoordinateReferenceSystem& theDestCRS, - double theMaxSrcXRes, double theMaxSrcYRes, - const QgsRectangle& theExtent ) +QgsRasterProjector::QgsRasterProjector() : QgsRasterInterface( nullptr ) - , mSrcCRS( theSrcCRS ) - , mDestCRS( theDestCRS ) , mSrcDatumTransform( -1 ) , mDestDatumTransform( -1 ) - , mExtent( theExtent ) - , mDestRows( 0 ) - , mDestCols( 0 ) - , mDestXRes( 0.0 ) - , mDestYRes( 0.0 ) - , mSrcRows( 0 ) - , mSrcCols( 0 ) - , mSrcXRes( 0.0 ) - , mSrcYRes( 0.0 ) - , mDestRowsPerMatrixRow( 0.0 ) - , mDestColsPerMatrixCol( 0.0 ) - , pHelperTop( nullptr ), pHelperBottom( nullptr ) - , mHelperTopRow( 0 ) - , mCPCols( 0 ) - , mCPRows( 0 ) - , mSqrTolerance( 0.0 ) - , mMaxSrcXRes( theMaxSrcXRes ) - , mMaxSrcYRes( theMaxSrcYRes ) , mPrecision( Approximate ) , mApproximate( false ) -{ - QgsDebugMsgLevel( "Entered", 4 ); -} - -QgsRasterProjector::QgsRasterProjector() - : QgsRasterInterface( nullptr ) - , mSrcDatumTransform( -1 ) - , mDestDatumTransform( -1 ) , mDestRows( 0 ) , mDestCols( 0 ) , mDestXRes( 0.0 ) @@ -133,64 +49,22 @@ QgsRasterProjector::QgsRasterProjector() , mSqrTolerance( 0.0 ) , mMaxSrcXRes( 0 ) , mMaxSrcYRes( 0 ) - , mPrecision( Approximate ) - , mApproximate( false ) { QgsDebugMsgLevel( "Entered", 4 ); } -QgsRasterProjector::QgsRasterProjector( const QgsRasterProjector &projector ) - : QgsRasterInterface( nullptr ) - , pHelperTop( nullptr ) - , pHelperBottom( nullptr ) - , mHelperTopRow( 0 ) - , mCPCols( 0 ) - , mCPRows( 0 ) - , mSqrTolerance( 0 ) - , mApproximate( false ) -{ - mSrcCRS = projector.mSrcCRS; - mDestCRS = projector.mDestCRS; - mSrcDatumTransform = projector.mSrcDatumTransform; - mDestDatumTransform = projector.mDestDatumTransform; - mMaxSrcXRes = projector.mMaxSrcXRes; - mMaxSrcYRes = projector.mMaxSrcYRes; - mExtent = projector.mExtent; - mDestRows = projector.mDestRows; - mDestCols = projector.mDestCols; - mDestXRes = projector.mDestXRes; - mDestYRes = projector.mDestYRes; - mSrcRows = projector.mSrcRows; - mSrcCols = projector.mSrcCols; - mSrcXRes = projector.mSrcXRes; - mSrcYRes = projector.mSrcYRes; - mDestRowsPerMatrixRow = projector.mDestRowsPerMatrixRow; - mDestColsPerMatrixCol = projector.mDestColsPerMatrixCol; - mPrecision = projector.mPrecision; -} - -QgsRasterProjector & QgsRasterProjector::operator=( const QgsRasterProjector & projector ) -{ - if ( &projector != this ) - { - mSrcCRS = projector.mSrcCRS; - mDestCRS = projector.mDestCRS; - mSrcDatumTransform = projector.mSrcDatumTransform; - mDestDatumTransform = projector.mDestDatumTransform; - mMaxSrcXRes = projector.mMaxSrcXRes; - mMaxSrcYRes = projector.mMaxSrcYRes; - mExtent = projector.mExtent; - mPrecision = projector.mPrecision; - } - return *this; -} QgsRasterProjector* QgsRasterProjector::clone() const { QgsDebugMsgLevel( "Entered", 4 ); - QgsRasterProjector * projector = new QgsRasterProjector( mSrcCRS, mDestCRS, mMaxSrcXRes, mMaxSrcYRes, mExtent ); + QgsRasterProjector * projector = new QgsRasterProjector; + projector->mSrcCRS = mSrcCRS; + projector->mDestCRS = mDestCRS; projector->mSrcDatumTransform = mSrcDatumTransform; projector->mDestDatumTransform = mDestDatumTransform; + projector->mMaxSrcXRes = mMaxSrcXRes; + projector->mMaxSrcYRes = mMaxSrcYRes; + projector->mExtent = mExtent; projector->mPrecision = mPrecision; return projector; } @@ -897,17 +771,17 @@ QgsRasterBlock * QgsRasterProjector::block( int bandNo, QgsRectangle const & ex mDestCols = width; calc(); - QgsDebugMsgLevel( QString( "srcExtent:\n%1" ).arg( srcExtent().toString() ), 4 ); - QgsDebugMsgLevel( QString( "srcCols = %1 srcRows = %2" ).arg( srcCols() ).arg( srcRows() ), 4 ); + QgsDebugMsgLevel( QString( "srcExtent:\n%1" ).arg( mSrcExtent.toString() ), 4 ); + QgsDebugMsgLevel( QString( "srcCols = %1 srcRows = %2" ).arg( mSrcCols ).arg( mSrcRows ), 4 ); // If we zoom out too much, projector srcRows / srcCols maybe 0, which can cause problems in providers - if ( srcRows() <= 0 || srcCols() <= 0 ) + if ( mSrcRows <= 0 || mSrcCols <= 0 ) { QgsDebugMsgLevel( "Zero srcRows or srcCols", 4 ); return new QgsRasterBlock(); } - QgsRasterBlock *inputBlock = mInput->block( bandNo, srcExtent(), srcCols(), srcRows(), feedback ); + QgsRasterBlock *inputBlock = mInput->block( bandNo, mSrcExtent, mSrcCols, mSrcRows, feedback ); if ( !inputBlock || inputBlock->isEmpty() ) { QgsDebugMsg( "No raster data!" ); diff --git a/src/core/raster/qgsrasterprojector.h b/src/core/raster/qgsrasterprojector.h index 0b6d18dfe0ba..9682d758ce49 100644 --- a/src/core/raster/qgsrasterprojector.h +++ b/src/core/raster/qgsrasterprojector.h @@ -36,6 +36,9 @@ class QgsPoint; class QgsCoordinateTransform; /** \ingroup core + * \brief QgsRasterProjector implements approximate projection support for + * it calculates grid of points in source CRS for target CRS + extent + * which are used to calculate affine transformation matrices. * \class QgsRasterProjector */ class CORE_EXPORT QgsRasterProjector : public QgsRasterInterface @@ -50,44 +53,11 @@ class CORE_EXPORT QgsRasterProjector : public QgsRasterInterface Exact = 1, //!< Exact, precise but slow }; - /** \brief QgsRasterProjector implements approximate projection support for - * it calculates grid of points in source CRS for target CRS + extent - * which are used to calculate affine transformation matrices. - */ - - QgsRasterProjector( const QgsCoordinateReferenceSystem& theSrcCRS, - const QgsCoordinateReferenceSystem& theDestCRS, - int theSrcDatumTransform, - int theDestDatumTransform, - const QgsRectangle& theDestExtent, - int theDestRows, int theDestCols, - double theMaxSrcXRes, double theMaxSrcYRes, - const QgsRectangle& theExtent - ); - - QgsRasterProjector( const QgsCoordinateReferenceSystem& theSrcCRS, - const QgsCoordinateReferenceSystem& theDestCRS, - const QgsRectangle& theDestExtent, - int theDestRows, int theDestCols, - double theMaxSrcXRes, double theMaxSrcYRes, - const QgsRectangle& theExtent - ); - QgsRasterProjector( const QgsCoordinateReferenceSystem& theSrcCRS, - const QgsCoordinateReferenceSystem& theDestCRS, - double theMaxSrcXRes, double theMaxSrcYRes, - const QgsRectangle& theExtent - ); QgsRasterProjector(); - /** \brief Copy constructor */ - // To avoid synthesized which fails on copy of QgsCoordinateTransform - // (QObject child) in Python bindings - QgsRasterProjector( const QgsRasterProjector &projector ); /** \brief The destructor */ ~QgsRasterProjector(); - QgsRasterProjector & operator=( const QgsRasterProjector &projector ); - QgsRasterProjector *clone() const override; int bandCount() const override; @@ -128,14 +98,6 @@ class CORE_EXPORT QgsRasterProjector : public QgsRasterInterface QgsRectangle& theDestExtent, int& theDestXSize, int& theDestYSize ); private: - /** Get source extent */ - QgsRectangle srcExtent() { return mSrcExtent; } - - /** Get/set source width/height */ - int srcRows() { return mSrcRows; } - int srcCols() { return mSrcCols; } - void setSrcRows( int theRows ) { mSrcRows = theRows; mSrcXRes = mSrcExtent.height() / mSrcRows; } - void setSrcCols( int theCols ) { mSrcCols = theCols; mSrcYRes = mSrcExtent.width() / mSrcCols; } /** \brief Get source row and column indexes for current source extent and resolution If source pixel is outside source extent theSrcRow and theSrcCol are left unchanged. @@ -143,9 +105,6 @@ class CORE_EXPORT QgsRasterProjector : public QgsRasterInterface */ bool srcRowCol( int theDestRow, int theDestCol, int *theSrcRow, int *theSrcCol, const QgsCoordinateTransform& ct ); - int dstRows() const { return mDestRows; } - int dstCols() const { return mDestCols; } - /** \brief get destination point for _current_ destination position */ void destPointOnCPMatrix( int theRow, int theCol, double *theX, double *theY ); @@ -215,6 +174,16 @@ class CORE_EXPORT QgsRasterProjector : public QgsRasterInterface /** Destination datum transformation id (or -1 if none) */ int mDestDatumTransform; + /** Requested precision */ + Precision mPrecision; + + /** Use approximation (requested precision is Approximate and it is possible to calculate + * an approximation matrix with a sufficient precision) */ + bool mApproximate; + + + + /** Destination extent */ QgsRectangle mDestExtent; @@ -284,12 +253,6 @@ class CORE_EXPORT QgsRasterProjector : public QgsRasterInterface double mMaxSrcXRes; double mMaxSrcYRes; - /** Requested precision */ - Precision mPrecision; - - /** Use approximation (requested precision is Approximate and it is possible to calculate - * an approximation matrix with a sufficient precision) */ - bool mApproximate; }; #endif From fdeac8198c91c2a15385c124b8298c14677ca431 Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Thu, 1 Sep 2016 18:25:01 +0800 Subject: [PATCH 09/26] Fix incorrect raster reprojection after drawing raster previews Moved all temporary projector members to a private class, so even recursive block() calls will not affect each other (there is no new performance penalty as block() call always recomputes the temporary control point matrix anyway) --- python/core/raster/qgsrasterprojector.sip | 3 - src/core/raster/qgsrasterprojector.cpp | 151 ++++++++++------------ src/core/raster/qgsrasterprojector.h | 74 ++++++----- 3 files changed, 108 insertions(+), 120 deletions(-) diff --git a/python/core/raster/qgsrasterprojector.sip b/python/core/raster/qgsrasterprojector.sip index 813eadfbbf52..bbc23587be34 100644 --- a/python/core/raster/qgsrasterprojector.sip +++ b/python/core/raster/qgsrasterprojector.sip @@ -43,9 +43,6 @@ class QgsRasterProjector : QgsRasterInterface /** \brief Get destination CRS */ QgsCoordinateReferenceSystem destinationCrs() const; - /** \brief set maximum source resolution */ - void setMaxSrcRes( double theMaxSrcXRes, double theMaxSrcYRes ); - Precision precision() const; void setPrecision( Precision precision ); // Translated precision mode, for use in ComboBox etc. diff --git a/src/core/raster/qgsrasterprojector.cpp b/src/core/raster/qgsrasterprojector.cpp index 626333b6f8d7..37315926ad6b 100644 --- a/src/core/raster/qgsrasterprojector.cpp +++ b/src/core/raster/qgsrasterprojector.cpp @@ -24,31 +24,11 @@ #include "qgscsexception.h" - QgsRasterProjector::QgsRasterProjector() : QgsRasterInterface( nullptr ) , mSrcDatumTransform( -1 ) , mDestDatumTransform( -1 ) , mPrecision( Approximate ) - , mApproximate( false ) - , mDestRows( 0 ) - , mDestCols( 0 ) - , mDestXRes( 0.0 ) - , mDestYRes( 0.0 ) - , mSrcRows( 0 ) - , mSrcCols( 0 ) - , mSrcXRes( 0.0 ) - , mSrcYRes( 0.0 ) - , mDestRowsPerMatrixRow( 0.0 ) - , mDestColsPerMatrixCol( 0.0 ) - , pHelperTop( nullptr ) - , pHelperBottom( nullptr ) - , mHelperTopRow( 0 ) - , mCPCols( 0 ) - , mCPRows( 0 ) - , mSqrTolerance( 0.0 ) - , mMaxSrcXRes( 0 ) - , mMaxSrcYRes( 0 ) { QgsDebugMsgLevel( "Entered", 4 ); } @@ -62,17 +42,12 @@ QgsRasterProjector* QgsRasterProjector::clone() const projector->mDestCRS = mDestCRS; projector->mSrcDatumTransform = mSrcDatumTransform; projector->mDestDatumTransform = mDestDatumTransform; - projector->mMaxSrcXRes = mMaxSrcXRes; - projector->mMaxSrcYRes = mMaxSrcYRes; - projector->mExtent = mExtent; projector->mPrecision = mPrecision; return projector; } QgsRasterProjector::~QgsRasterProjector() { - delete[] pHelperTop; - delete[] pHelperBottom; } int QgsRasterProjector::bandCount() const @@ -97,22 +72,36 @@ void QgsRasterProjector::setCrs( const QgsCoordinateReferenceSystem & theSrcCRS, mDestDatumTransform = destDatumTransform; } -void QgsRasterProjector::calc() + +ProjectorData::ProjectorData( const QgsRectangle& extent, int width, int height, QgsRasterInterface* input, const QgsCoordinateTransform& inverseCt, QgsRasterProjector::Precision precision ) + : mApproximate( false ) + , mInverseCt( new QgsCoordinateTransform( inverseCt ) ) + , mDestExtent( extent ) + , mDestRows( height ) + , mDestCols( width ) + , mDestXRes( 0.0 ) + , mDestYRes( 0.0 ) + , mSrcRows( 0 ) + , mSrcCols( 0 ) + , mSrcXRes( 0.0 ) + , mSrcYRes( 0.0 ) + , mDestRowsPerMatrixRow( 0.0 ) + , mDestColsPerMatrixCol( 0.0 ) + , pHelperTop( nullptr ) + , pHelperBottom( nullptr ) + , mHelperTopRow( 0 ) + , mCPCols( 0 ) + , mCPRows( 0 ) + , mSqrTolerance( 0.0 ) + , mMaxSrcXRes( 0 ) + , mMaxSrcYRes( 0 ) { QgsDebugMsgLevel( "Entered", 4 ); - mCPMatrix.clear(); - mCPLegalMatrix.clear(); - delete[] pHelperTop; - pHelperTop = nullptr; - delete[] pHelperBottom; - pHelperBottom = nullptr; // Get max source resolution and extent if possible - mMaxSrcXRes = 0; - mMaxSrcYRes = 0; - if ( mInput ) + if ( input ) { - QgsRasterDataProvider *provider = dynamic_cast( mInput->sourceInput() ); + QgsRasterDataProvider *provider = dynamic_cast( input->sourceInput() ); if ( provider ) { if ( provider->capabilities() & QgsRasterDataProvider::Size ) @@ -138,9 +127,7 @@ void QgsRasterProjector::calc() double myDestRes = mDestXRes < mDestYRes ? mDestXRes : mDestYRes; mSqrTolerance = myDestRes * myDestRes; - QgsCoordinateTransform inverseCt = QgsCoordinateTransformCache::instance()->transform( mDestCRS.authid(), mSrcCRS.authid(), mDestDatumTransform, mSrcDatumTransform ); - - if ( mPrecision == Approximate ) + if ( precision == QgsRasterProjector::Approximate ) { mApproximate = true; } @@ -219,7 +206,15 @@ void QgsRasterProjector::calc() mSrcXRes = mSrcExtent.width() / mSrcCols; } -void QgsRasterProjector::calcSrcExtent() +ProjectorData::~ProjectorData() +{ + delete[] pHelperTop; + delete[] pHelperBottom; + delete mInverseCt; +} + + +void ProjectorData::calcSrcExtent() { /* Run around the mCPMatrix and find source extent */ // Attention, source limits are not necessarily on destination edges, e.g. @@ -283,7 +278,7 @@ void QgsRasterProjector::calcSrcExtent() QgsDebugMsgLevel( "mSrcExtent = " + mSrcExtent.toString(), 4 ); } -QString QgsRasterProjector::cpToString() +QString ProjectorData::cpToString() { QString myString; for ( int i = 0; i < mCPRows; i++ ) @@ -308,7 +303,7 @@ QString QgsRasterProjector::cpToString() return myString; } -void QgsRasterProjector::calcSrcRowsCols() +void ProjectorData::calcSrcRowsCols() { // Wee need to calculate minimum cell size in the source // TODO: Think it over better, what is the right source resolution? @@ -347,11 +342,10 @@ void QgsRasterProjector::calcSrcRowsCols() else { // take highest from corners, points in in the middle of corners and center (3 x 3 ) - QgsCoordinateTransform inverseCt = QgsCoordinateTransformCache::instance()->transform( mDestCRS.authid(), mSrcCRS.authid(), mDestDatumTransform, mSrcDatumTransform ); //double QgsRectangle srcExtent; int srcXSize, srcYSize; - if ( extentSize( inverseCt, mDestExtent, mDestCols, mDestRows, srcExtent, srcXSize, srcYSize ) ) + if ( QgsRasterProjector::extentSize( *mInverseCt, mDestExtent, mDestCols, mDestRows, srcExtent, srcXSize, srcYSize ) ) { double srcXRes = srcExtent.width() / srcXSize; double srcYRes = srcExtent.height() / srcYSize; @@ -383,29 +377,22 @@ void QgsRasterProjector::calcSrcRowsCols() } -inline void QgsRasterProjector::destPointOnCPMatrix( int theRow, int theCol, double *theX, double *theY ) +inline void ProjectorData::destPointOnCPMatrix( int theRow, int theCol, double *theX, double *theY ) { *theX = mDestExtent.xMinimum() + theCol * mDestExtent.width() / ( mCPCols - 1 ); *theY = mDestExtent.yMaximum() - theRow * mDestExtent.height() / ( mCPRows - 1 ); } -inline int QgsRasterProjector::matrixRow( int theDestRow ) +inline int ProjectorData::matrixRow( int theDestRow ) { return static_cast< int >( floor(( theDestRow + 0.5 ) / mDestRowsPerMatrixRow ) ); } -inline int QgsRasterProjector::matrixCol( int theDestCol ) +inline int ProjectorData::matrixCol( int theDestCol ) { return static_cast< int >( floor(( theDestCol + 0.5 ) / mDestColsPerMatrixCol ) ); } -QgsPoint QgsRasterProjector::srcPoint( int theDestRow, int theCol ) -{ - Q_UNUSED( theDestRow ); - Q_UNUSED( theCol ); - return QgsPoint(); -} - -void QgsRasterProjector::calcHelper( int theMatrixRow, QgsPoint *thePoints ) +void ProjectorData::calcHelper( int theMatrixRow, QgsPoint *thePoints ) { // TODO?: should we also precalc dest cell center coordinates for x and y? for ( int myDestCol = 0; myDestCol < mDestCols; myDestCol++ ) @@ -430,7 +417,8 @@ void QgsRasterProjector::calcHelper( int theMatrixRow, QgsPoint *thePoints ) thePoints[myDestCol].setY( t ); } } -void QgsRasterProjector::nextHelper() + +void ProjectorData::nextHelper() { // We just switch pHelperTop and pHelperBottom, memory is not lost QgsPoint *tmp; @@ -441,7 +429,7 @@ void QgsRasterProjector::nextHelper() mHelperTopRow++; } -bool QgsRasterProjector::srcRowCol( int theDestRow, int theDestCol, int *theSrcRow, int *theSrcCol, const QgsCoordinateTransform& ct ) +bool ProjectorData::srcRowCol( int theDestRow, int theDestCol, int *theSrcRow, int *theSrcCol ) { if ( mApproximate ) { @@ -449,11 +437,11 @@ bool QgsRasterProjector::srcRowCol( int theDestRow, int theDestCol, int *theSrcR } else { - return preciseSrcRowCol( theDestRow, theDestCol, theSrcRow, theSrcCol, ct ); + return preciseSrcRowCol( theDestRow, theDestCol, theSrcRow, theSrcCol ); } } -bool QgsRasterProjector::preciseSrcRowCol( int theDestRow, int theDestCol, int *theSrcRow, int *theSrcCol, const QgsCoordinateTransform& ct ) +bool ProjectorData::preciseSrcRowCol( int theDestRow, int theDestCol, int *theSrcRow, int *theSrcCol ) { #ifdef QGISDEBUG QgsDebugMsgLevel( QString( "theDestRow = %1" ).arg( theDestRow ), 5 ); @@ -469,9 +457,9 @@ bool QgsRasterProjector::preciseSrcRowCol( int theDestRow, int theDestCol, int * QgsDebugMsgLevel( QString( "x = %1 y = %2" ).arg( x ).arg( y ), 5 ); #endif - if ( ct.isValid() ) + if ( mInverseCt->isValid() ) { - ct.transformInPlace( x, y, z ); + mInverseCt->transformInPlace( x, y, z ); } #ifdef QGISDEBUG @@ -502,7 +490,7 @@ bool QgsRasterProjector::preciseSrcRowCol( int theDestRow, int theDestCol, int * return true; } -bool QgsRasterProjector::approximateSrcRowCol( int theDestRow, int theDestCol, int *theSrcRow, int *theSrcCol ) +bool ProjectorData::approximateSrcRowCol( int theDestRow, int theDestCol, int *theSrcRow, int *theSrcCol ) { int myMatrixRow = matrixRow( theDestRow ); int myMatrixCol = matrixCol( theDestCol ); @@ -559,7 +547,7 @@ bool QgsRasterProjector::approximateSrcRowCol( int theDestRow, int theDestCol, i return true; } -void QgsRasterProjector::insertRows( const QgsCoordinateTransform& ct ) +void ProjectorData::insertRows( const QgsCoordinateTransform& ct ) { for ( int r = 0; r < mCPRows - 1; r++ ) { @@ -583,7 +571,7 @@ void QgsRasterProjector::insertRows( const QgsCoordinateTransform& ct ) } } -void QgsRasterProjector::insertCols( const QgsCoordinateTransform& ct ) +void ProjectorData::insertCols( const QgsCoordinateTransform& ct ) { for ( int r = 0; r < mCPRows; r++ ) { @@ -601,7 +589,7 @@ void QgsRasterProjector::insertCols( const QgsCoordinateTransform& ct ) } -void QgsRasterProjector::calcCP( int theRow, int theCol, const QgsCoordinateTransform& ct ) +void ProjectorData::calcCP( int theRow, int theCol, const QgsCoordinateTransform& ct ) { double myDestX, myDestY; destPointOnCPMatrix( theRow, theCol, &myDestX, &myDestY ); @@ -626,7 +614,7 @@ void QgsRasterProjector::calcCP( int theRow, int theCol, const QgsCoordinateTran } } -bool QgsRasterProjector::calcRow( int theRow, const QgsCoordinateTransform& ct ) +bool ProjectorData::calcRow( int theRow, const QgsCoordinateTransform& ct ) { QgsDebugMsgLevel( QString( "theRow = %1" ).arg( theRow ), 3 ); for ( int i = 0; i < mCPCols; i++ ) @@ -637,7 +625,7 @@ bool QgsRasterProjector::calcRow( int theRow, const QgsCoordinateTransform& ct ) return true; } -bool QgsRasterProjector::calcCol( int theCol, const QgsCoordinateTransform& ct ) +bool ProjectorData::calcCol( int theCol, const QgsCoordinateTransform& ct ) { QgsDebugMsgLevel( QString( "theCol = %1" ).arg( theCol ), 3 ); for ( int i = 0; i < mCPRows; i++ ) @@ -648,7 +636,7 @@ bool QgsRasterProjector::calcCol( int theCol, const QgsCoordinateTransform& ct ) return true; } -bool QgsRasterProjector::checkCols( const QgsCoordinateTransform& ct ) +bool ProjectorData::checkCols( const QgsCoordinateTransform& ct ) { if ( !ct.isValid() ) { @@ -693,7 +681,7 @@ bool QgsRasterProjector::checkCols( const QgsCoordinateTransform& ct ) return true; } -bool QgsRasterProjector::checkRows( const QgsCoordinateTransform& ct ) +bool ProjectorData::checkRows( const QgsCoordinateTransform& ct ) { if ( !ct.isValid() ) { @@ -766,22 +754,21 @@ QgsRasterBlock * QgsRasterProjector::block( int bandNo, QgsRectangle const & ex return mInput->block( bandNo, extent, width, height, feedback ); } - mDestExtent = extent; - mDestRows = height; - mDestCols = width; - calc(); + QgsCoordinateTransform inverseCt = QgsCoordinateTransformCache::instance()->transform( mDestCRS.authid(), mSrcCRS.authid(), mDestDatumTransform, mSrcDatumTransform ); + + ProjectorData pd( extent, width, height, mInput, inverseCt, mPrecision ); - QgsDebugMsgLevel( QString( "srcExtent:\n%1" ).arg( mSrcExtent.toString() ), 4 ); - QgsDebugMsgLevel( QString( "srcCols = %1 srcRows = %2" ).arg( mSrcCols ).arg( mSrcRows ), 4 ); + QgsDebugMsgLevel( QString( "srcExtent:\n%1" ).arg( pd.srcExtent().toString() ), 4 ); + QgsDebugMsgLevel( QString( "srcCols = %1 srcRows = %2" ).arg( pd.srcCols() ).arg( pd.srcRows() ), 4 ); // If we zoom out too much, projector srcRows / srcCols maybe 0, which can cause problems in providers - if ( mSrcRows <= 0 || mSrcCols <= 0 ) + if ( pd.srcRows() <= 0 || pd.srcCols() <= 0 ) { QgsDebugMsgLevel( "Zero srcRows or srcCols", 4 ); return new QgsRasterBlock(); } - QgsRasterBlock *inputBlock = mInput->block( bandNo, mSrcExtent, mSrcCols, mSrcRows, feedback ); + QgsRasterBlock *inputBlock = mInput->block( bandNo, pd.srcExtent(), pd.srcCols(), pd.srcRows(), feedback ); if ( !inputBlock || inputBlock->isEmpty() ) { QgsDebugMsg( "No raster data!" ); @@ -822,12 +809,6 @@ QgsRasterBlock * QgsRasterProjector::block( int bandNo, QgsRectangle const & ex // we cannot fill output block with no data because we use memcpy for data, not setValue(). bool doNoData = !QgsRasterBlock::typeIsNumeric( inputBlock->dataType() ) && inputBlock->hasNoData() && !inputBlock->hasNoDataValue(); - QgsCoordinateTransform inverseCt; - if ( !mApproximate ) - { - inverseCt = QgsCoordinateTransformCache::instance()->transform( mDestCRS.authid(), mSrcCRS.authid(), mDestDatumTransform, mSrcDatumTransform ); - } - outputBlock->setIsNoData(); int srcRow, srcCol; @@ -835,10 +816,10 @@ QgsRasterBlock * QgsRasterProjector::block( int bandNo, QgsRectangle const & ex { for ( int j = 0; j < width; ++j ) { - bool inside = srcRowCol( i, j, &srcRow, &srcCol, inverseCt ); + bool inside = pd.srcRowCol( i, j, &srcRow, &srcCol ); if ( !inside ) continue; // we have everything set to no data - qgssize srcIndex = static_cast< qgssize >( srcRow ) * mSrcCols + srcCol; + qgssize srcIndex = static_cast< qgssize >( srcRow ) * pd.srcCols() + srcCol; QgsDebugMsgLevel( QString( "row = %1 col = %2 srcRow = %3 srcCol = %4" ).arg( i ).arg( j ).arg( srcRow ).arg( srcCol ), 5 ); // isNoData() may be slow so we check doNoData first diff --git a/src/core/raster/qgsrasterprojector.h b/src/core/raster/qgsrasterprojector.h index 9682d758ce49..b7832a68918d 100644 --- a/src/core/raster/qgsrasterprojector.h +++ b/src/core/raster/qgsrasterprojector.h @@ -74,13 +74,6 @@ class CORE_EXPORT QgsRasterProjector : public QgsRasterInterface /** \brief Get destination CRS */ QgsCoordinateReferenceSystem destinationCrs() const { return mDestCRS; } - /** \brief set maximum source resolution */ - void setMaxSrcRes( double theMaxSrcXRes, double theMaxSrcYRes ) - { - mMaxSrcXRes = theMaxSrcXRes; - mMaxSrcYRes = theMaxSrcYRes; - } - Precision precision() const { return mPrecision; } void setPrecision( Precision precision ) { mPrecision = precision; } // Translated precision mode, for use in ComboBox etc. @@ -99,12 +92,48 @@ class CORE_EXPORT QgsRasterProjector : public QgsRasterInterface private: + /** Source CRS */ + QgsCoordinateReferenceSystem mSrcCRS; + + /** Destination CRS */ + QgsCoordinateReferenceSystem mDestCRS; + + /** Source datum transformation id (or -1 if none) */ + int mSrcDatumTransform; + + /** Destination datum transformation id (or -1 if none) */ + int mDestDatumTransform; + + /** Requested precision */ + Precision mPrecision; + +}; + +/// @cond PRIVATE + +/** + * Internal class for reprojection of rasters - either exact or approximate. + * QgsRasterProjector creates it and then keeps calling srcRowCol() to get source pixel position + * for every destination pixel position. + */ +class ProjectorData +{ + public: + /** Initialize reprojector and calculate matrix */ + ProjectorData( const QgsRectangle &extent, int width, int height, QgsRasterInterface *input, const QgsCoordinateTransform &inverseCt, QgsRasterProjector::Precision precision ); + ~ProjectorData(); + /** \brief Get source row and column indexes for current source extent and resolution If source pixel is outside source extent theSrcRow and theSrcCol are left unchanged. @return true if inside source */ - bool srcRowCol( int theDestRow, int theDestCol, int *theSrcRow, int *theSrcCol, const QgsCoordinateTransform& ct ); + bool srcRowCol( int theDestRow, int theDestCol, int *theSrcRow, int *theSrcCol ); + + QgsRectangle srcExtent() const { return mSrcExtent; } + int srcRows() const { return mSrcRows; } + int srcCols() const { return mSrcCols; } + private: /** \brief get destination point for _current_ destination position */ void destPointOnCPMatrix( int theRow, int theCol, double *theX, double *theY ); @@ -112,18 +141,12 @@ class CORE_EXPORT QgsRasterProjector : public QgsRasterInterface int matrixRow( int theDestRow ); int matrixCol( int theDestCol ); - /** \brief get destination point for _current_ matrix position */ - QgsPoint srcPoint( int theRow, int theCol ); - /** \brief Get precise source row and column indexes for current source extent and resolution */ - inline bool preciseSrcRowCol( int theDestRow, int theDestCol, int *theSrcRow, int *theSrcCol, const QgsCoordinateTransform& ct ); + inline bool preciseSrcRowCol( int theDestRow, int theDestCol, int *theSrcRow, int *theSrcCol ); /** \brief Get approximate source row and column indexes for current source extent and resolution */ inline bool approximateSrcRowCol( int theDestRow, int theDestCol, int *theSrcRow, int *theSrcCol ); - /** \brief Calculate matrix */ - void calc(); - /** \brief insert rows to matrix */ void insertRows( const QgsCoordinateTransform& ct ); @@ -162,27 +185,12 @@ class CORE_EXPORT QgsRasterProjector : public QgsRasterInterface /** Get mCPMatrix as string */ QString cpToString(); - /** Source CRS */ - QgsCoordinateReferenceSystem mSrcCRS; - - /** Destination CRS */ - QgsCoordinateReferenceSystem mDestCRS; - - /** Source datum transformation id (or -1 if none) */ - int mSrcDatumTransform; - - /** Destination datum transformation id (or -1 if none) */ - int mDestDatumTransform; - - /** Requested precision */ - Precision mPrecision; - /** Use approximation (requested precision is Approximate and it is possible to calculate * an approximation matrix with a sufficient precision) */ bool mApproximate; - - + /** Transformation from destination CRS to source CRS */ + QgsCoordinateTransform* mInverseCt; /** Destination extent */ QgsRectangle mDestExtent; @@ -255,5 +263,7 @@ class CORE_EXPORT QgsRasterProjector : public QgsRasterInterface }; +/// @endcond + #endif From 8d832d8aca7b29b67ef274f641a02ac3cc3c87d9 Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Thu, 1 Sep 2016 21:43:40 +0800 Subject: [PATCH 10/26] Make sure we do not miss cancellation request at the beginning --- src/providers/wms/qgswmsprovider.cpp | 33 +++++++++++++++++++++++----- src/providers/wms/qgswmsprovider.h | 2 ++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/providers/wms/qgswmsprovider.cpp b/src/providers/wms/qgswmsprovider.cpp index 35c4665a6350..e6d732964c8b 100644 --- a/src/providers/wms/qgswmsprovider.cpp +++ b/src/providers/wms/qgswmsprovider.cpp @@ -3337,7 +3337,18 @@ QgsWmsImageDownloadHandler::QgsWmsImageDownloadHandler( const QString& providerU : mProviderUri( providerUri ) , mCachedImage( image ) , mEventLoop( new QEventLoop ) + , mFeedback( feedback ) { + if ( feedback ) + { + connect( feedback, SIGNAL( cancelled() ), this, SLOT( cancelled() ), Qt::QueuedConnection ); + + // rendering could have been cancelled before we started to listen to cancelled() signal + // so let's check before doing the download and maybe quit prematurely + if ( feedback->isCancelled() ) + return; + } + QNetworkRequest request( url ); auth.setAuthorization( request ); request.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true ); @@ -3346,9 +3357,6 @@ QgsWmsImageDownloadHandler::QgsWmsImageDownloadHandler( const QString& providerU connect( mCacheReply, SIGNAL( downloadProgress( qint64, qint64 ) ), this, SLOT( cacheReplyProgress( qint64, qint64 ) ) ); Q_ASSERT( mCacheReply->thread() == QThread::currentThread() ); - - if ( feedback ) - connect( feedback, SIGNAL( cancelled() ), this, SLOT( cancelled() ), Qt::QueuedConnection ); } QgsWmsImageDownloadHandler::~QgsWmsImageDownloadHandler() @@ -3358,6 +3366,9 @@ QgsWmsImageDownloadHandler::~QgsWmsImageDownloadHandler() void QgsWmsImageDownloadHandler::downloadBlocking() { + if ( mFeedback && mFeedback->isCancelled() ) + return; // nothing to do + mEventLoop->exec( QEventLoop::ExcludeUserInputEvents ); Q_ASSERT( !mCacheReply ); @@ -3497,6 +3508,16 @@ QgsWmsTiledImageDownloadHandler::QgsWmsTiledImageDownloadHandler( const QString& , mSmoothPixmapTransform( smoothPixmapTransform ) , mFeedback( feedback ) { + if ( feedback ) + { + connect( feedback, SIGNAL( cancelled() ), this, SLOT( cancelled() ), Qt::QueuedConnection ); + + // rendering could have been cancelled before we started to listen to cancelled() signal + // so let's check before doing the download and maybe quit prematurely + if ( feedback->isCancelled() ) + return; + } + Q_FOREACH ( const TileRequest& r, requests ) { QNetworkRequest request( r.url ); @@ -3513,9 +3534,6 @@ QgsWmsTiledImageDownloadHandler::QgsWmsTiledImageDownloadHandler( const QString& mReplies << reply; } - - if ( feedback ) - connect( feedback, SIGNAL( cancelled() ), this, SLOT( cancelled() ), Qt::QueuedConnection ); } QgsWmsTiledImageDownloadHandler::~QgsWmsTiledImageDownloadHandler() @@ -3525,6 +3543,9 @@ QgsWmsTiledImageDownloadHandler::~QgsWmsTiledImageDownloadHandler() void QgsWmsTiledImageDownloadHandler::downloadBlocking() { + if ( mFeedback && mFeedback->isCancelled() ) + return; // nothing to do + mEventLoop->exec( QEventLoop::ExcludeUserInputEvents ); Q_ASSERT( mReplies.isEmpty() ); diff --git a/src/providers/wms/qgswmsprovider.h b/src/providers/wms/qgswmsprovider.h index feb48d802c7b..9a137faa260a 100644 --- a/src/providers/wms/qgswmsprovider.h +++ b/src/providers/wms/qgswmsprovider.h @@ -571,6 +571,8 @@ class QgsWmsImageDownloadHandler : public QObject QImage* mCachedImage; QEventLoop* mEventLoop; + + QgsRasterBlockFeedback* mFeedback; }; From 80f022878f4698efe694acff3f53c1a556afbe3a Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Fri, 2 Sep 2016 09:41:35 +0800 Subject: [PATCH 11/26] Small code cleanups --- python/core/raster/qgsrasterinterface.sip | 16 +++++++++++- src/core/raster/qgsrasterinterface.h | 1 + src/core/raster/qgsrasterlayerrenderer.cpp | 6 ++--- src/core/raster/qgsrasterlayerrenderer.h | 30 ++++++++++++---------- 4 files changed, 35 insertions(+), 18 deletions(-) diff --git a/python/core/raster/qgsrasterinterface.sip b/python/core/raster/qgsrasterinterface.sip index b51a01336c8e..5d935a9caf32 100644 --- a/python/core/raster/qgsrasterinterface.sip +++ b/python/core/raster/qgsrasterinterface.sip @@ -9,7 +9,21 @@ class QgsRasterBlockFeedback : QgsFeedback %TypeHeaderCode #include %End - // TODO: extend with preview functionality?? + + public: + //! construct a new raster block feedback object + QgsRasterBlockFeedback( QObject* parent = nullptr ); + + //! whether the raster provider should return only data that are already available + //! without waiting for full result + bool preview_only; + + //! whether our painter is drawing to a temporary image used just by this layer + bool render_partial_output; + + //! may be emitted by raster data provider to indicate that some partial data are available + //! and a new preview image may be produced + virtual void onNewData(); }; diff --git a/src/core/raster/qgsrasterinterface.h b/src/core/raster/qgsrasterinterface.h index cdc8805022ab..931b0ff88095 100644 --- a/src/core/raster/qgsrasterinterface.h +++ b/src/core/raster/qgsrasterinterface.h @@ -37,6 +37,7 @@ class CORE_EXPORT QgsRasterBlockFeedback : public QgsFeedback { public: + //! construct a new raster block feedback object QgsRasterBlockFeedback( QObject* parent = nullptr ) : QgsFeedback( parent ), preview_only( false ), render_partial_output( false ) {} //! whether the raster provider should return only data that are already available diff --git a/src/core/raster/qgsrasterlayerrenderer.cpp b/src/core/raster/qgsrasterlayerrenderer.cpp index 33f6cbfd741a..1a1643302849 100644 --- a/src/core/raster/qgsrasterlayerrenderer.cpp +++ b/src/core/raster/qgsrasterlayerrenderer.cpp @@ -29,7 +29,7 @@ QgsRasterLayerRenderer::QgsRasterLayerRenderer( QgsRasterLayer* layer, QgsRender , mRasterViewPort( nullptr ) , mPipe( nullptr ) , mContext( rendererContext ) - , mFeedback( new MyFeedback( this ) ) + , mFeedback( new Feedback( this ) ) { mPainter = rendererContext.painter(); const QgsMapToPixel& theQgsMapToPixel = rendererContext.mapToPixel(); @@ -227,14 +227,14 @@ QgsFeedback* QgsRasterLayerRenderer::feedback() const return mFeedback; } -MyFeedback::MyFeedback( QgsRasterLayerRenderer *r ) +QgsRasterLayerRenderer::Feedback::Feedback( QgsRasterLayerRenderer *r ) : mR( r ) , mMinimalPreviewInterval( 250 ) { render_partial_output = r->mContext.testFlag( QgsRenderContext::RenderPartialOutput ); } -void MyFeedback::onNewData() +void QgsRasterLayerRenderer::Feedback::onNewData() { qDebug( "\nGOT NEW DATA!\n" ); diff --git a/src/core/raster/qgsrasterlayerrenderer.h b/src/core/raster/qgsrasterlayerrenderer.h index b4c30e3115b0..70b9b4ca1206 100644 --- a/src/core/raster/qgsrasterlayerrenderer.h +++ b/src/core/raster/qgsrasterlayerrenderer.h @@ -31,18 +31,6 @@ class QgsRasterLayerRenderer; #include "qgsrasterinterface.h" -class MyFeedback : public QgsRasterBlockFeedback -{ - public: - explicit MyFeedback( QgsRasterLayerRenderer* r ); - - virtual void onNewData() override; - private: - QgsRasterLayerRenderer* mR; - int mMinimalPreviewInterval; //!< in miliseconds - QTime mLastPreview; -}; - /** \ingroup core * Implementation of threaded rendering for raster layers. @@ -69,8 +57,22 @@ class QgsRasterLayerRenderer : public QgsMapLayerRenderer QgsRasterPipe* mPipe; QgsRenderContext& mContext; - MyFeedback* mFeedback; - friend class MyFeedback; + //! Specific feedback class to provide preview of raster layer rendering. + class Feedback : public QgsRasterBlockFeedback + { + public: + explicit Feedback( QgsRasterLayerRenderer* r ); + + //! when notified of new data in data provider it launches a preview draw of the raster + virtual void onNewData() override; + private: + QgsRasterLayerRenderer* mR; //!< parent renderer instance + int mMinimalPreviewInterval; //!< in miliseconds + QTime mLastPreview; //!< when last preview has been generated + }; + + //! feedback class for cancellation and preview generation + Feedback* mFeedback; }; From 7097345fead2a54073d2cf67c09422a43ccc8d3a Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Mon, 5 Sep 2016 11:16:53 +0800 Subject: [PATCH 12/26] Always use XYZ tile layers with smooth image transform It would make sense to have it enabled by default with WMTS too --- src/providers/wms/qgswmscapabilities.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/providers/wms/qgswmscapabilities.cpp b/src/providers/wms/qgswmscapabilities.cpp index a8bcaa55adb8..f1d1e0ade640 100644 --- a/src/providers/wms/qgswmscapabilities.cpp +++ b/src/providers/wms/qgswmscapabilities.cpp @@ -59,7 +59,7 @@ bool QgsWmsSettings::parseUri( const QString& uriString ) mAuth.mAuthCfg.clear(); mIgnoreGetMapUrl = false; mIgnoreGetFeatureInfoUrl = false; - mSmoothPixmapTransform = false; + mSmoothPixmapTransform = true; mDpiMode = dpiNone; // does not matter what we set here mActiveSubLayers = QStringList( "xyz" ); // just a placeholder to have one sub-layer mActiveSubStyles = QStringList( "xyz" ); // just a placeholder to have one sub-style From 2e0c35998fb54feb07480c8a251f6c867b7e80e9 Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Mon, 5 Sep 2016 12:37:16 +0800 Subject: [PATCH 13/26] Better tile loading order + small internal refactoring --- src/providers/wms/qgswmsprovider.cpp | 457 ++++++++++++++------------- src/providers/wms/qgswmsprovider.h | 42 ++- 2 files changed, 268 insertions(+), 231 deletions(-) diff --git a/src/providers/wms/qgswmsprovider.cpp b/src/providers/wms/qgswmsprovider.cpp index e6d732964c8b..4e16e8b643b1 100644 --- a/src/providers/wms/qgswmsprovider.cpp +++ b/src/providers/wms/qgswmsprovider.cpp @@ -83,11 +83,14 @@ QMap QgsWmsStatistics::sData; struct LessThanTileRequest { QgsPoint center; - bool operator()( const QgsWmsTiledImageDownloadHandler::TileRequest &req1, const QgsWmsTiledImageDownloadHandler::TileRequest &req2 ) + bool operator()( const QgsWmsProvider::TileRequest &req1, const QgsWmsProvider::TileRequest &req2 ) { QPointF p1 = req1.rect.center(); QPointF p2 = req2.rect.center(); - return center.distance( p1.x(), p1.y() ) < center.distance( p2.x(), p2.y() ); + // using chessboard distance (loading order more natural than euclidean/manhattan distance) + double d1 = qMax( qAbs( center.x() - p1.x() ), qAbs( center.y() - p1.y() ) ); + double d2 = qMax( qAbs( center.x() - p2.x() ), qAbs( center.y() - p2.y() ) ); + return d1 < d2; } }; @@ -506,78 +509,14 @@ QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, i { QgsDebugMsg( "Entering." ); - bool changeXY = mCaps.shouldInvertAxisOrientation( mImageCrs ); - // compose the URL query string for the WMS server. - QString bbox = toParamValue( viewExtent, changeXY ); - QImage* image = new QImage( pixelWidth, pixelHeight, QImage::Format_ARGB32 ); image->fill( 0 ); if ( !mSettings.mTiled && mSettings.mMaxWidth == 0 && mSettings.mMaxHeight == 0 ) { - // Calculate active layers that are also visible. - - QgsDebugMsg( "Active layer list of " + mSettings.mActiveSubLayers.join( ", " ) - + " and style list of " + mSettings.mActiveSubStyles.join( ", " ) ); - - QStringList visibleLayers = QStringList(); - QStringList visibleStyles = QStringList(); - - QStringList::const_iterator it2 = mSettings.mActiveSubStyles.constBegin(); - - for ( QStringList::const_iterator it = mSettings.mActiveSubLayers.constBegin(); - it != mSettings.mActiveSubLayers.constEnd(); - ++it ) - { - if ( mActiveSubLayerVisibility.constFind( *it ).value() ) - { - visibleLayers += *it; - visibleStyles += *it2; - } - - ++it2; - } - - QString layers = visibleLayers.join( "," ); - layers = layers.isNull() ? "" : layers; - QString styles = visibleStyles.join( "," ); - styles = styles.isNull() ? "" : styles; - - QgsDebugMsg( "Visible layer list of " + layers + " and style list of " + styles ); - - QUrl url( mSettings.mIgnoreGetMapUrl ? mSettings.mBaseUrl : getMapUrl() ); - setQueryItem( url, "SERVICE", "WMS" ); - setQueryItem( url, "VERSION", mCaps.mCapabilities.version ); - setQueryItem( url, "REQUEST", "GetMap" ); - setQueryItem( url, "BBOX", bbox ); - setSRSQueryItem( url ); - setQueryItem( url, "WIDTH", QString::number( pixelWidth ) ); - setQueryItem( url, "HEIGHT", QString::number( pixelHeight ) ); - setQueryItem( url, "LAYERS", layers ); - setQueryItem( url, "STYLES", styles ); - setFormatQueryItem( url ); - - if ( mDpi != -1 ) - { - if ( mSettings.mDpiMode & dpiQGIS ) - setQueryItem( url, "DPI", QString::number( mDpi ) ); - if ( mSettings.mDpiMode & dpiUMN ) - setQueryItem( url, "MAP_RESOLUTION", QString::number( mDpi ) ); - if ( mSettings.mDpiMode & dpiGeoServer ) - setQueryItem( url, "FORMAT_OPTIONS", QString( "dpi:%1" ).arg( mDpi ) ); - } - - //MH: jpeg does not support transparency and some servers complain if jpg and transparent=true - if ( mSettings.mImageMimeType == "image/x-jpegorpng" || - ( !mSettings.mImageMimeType.contains( "jpeg", Qt::CaseInsensitive ) && - !mSettings.mImageMimeType.contains( "jpg", Qt::CaseInsensitive ) ) ) - { - setQueryItem( url, "TRANSPARENT", "TRUE" ); // some servers giving error for 'true' (lowercase) - } - - QgsDebugMsg( QString( "getmap: %1" ).arg( url.toString() ) ); + QUrl url = createRequestUrlWMS( viewExtent, pixelWidth, pixelHeight ); // cache some details for if the user wants to do an identifyAsHtml() later @@ -672,161 +611,29 @@ QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, i } #endif - QList requests; + TilePositions tiles; + for ( int row = row0; row <= row1; row++ ) + { + for ( int col = col0; col <= col1; col++ ) + { + tiles << TilePosition( row, col ); + } + } + TileRequests requests; switch ( tileMode ) { case WMSC: - { - // add WMS request - QUrl url( mSettings.mIgnoreGetMapUrl ? mSettings.mBaseUrl : getMapUrl() ); - setQueryItem( url, "SERVICE", "WMS" ); - setQueryItem( url, "VERSION", mCaps.mCapabilities.version ); - setQueryItem( url, "REQUEST", "GetMap" ); - setQueryItem( url, "WIDTH", QString::number( tm->tileWidth ) ); - setQueryItem( url, "HEIGHT", QString::number( tm->tileHeight ) ); - setQueryItem( url, "LAYERS", mSettings.mActiveSubLayers.join( "," ) ); - setQueryItem( url, "STYLES", mSettings.mActiveSubStyles.join( "," ) ); - setFormatQueryItem( url ); - - setSRSQueryItem( url ); - - if ( mSettings.mTiled ) - { - setQueryItem( url, "TILED", "true" ); - } - - if ( mDpi != -1 ) - { - if ( mSettings.mDpiMode & dpiQGIS ) - setQueryItem( url, "DPI", QString::number( mDpi ) ); - if ( mSettings.mDpiMode & dpiUMN ) - setQueryItem( url, "MAP_RESOLUTION", QString::number( mDpi ) ); - if ( mSettings.mDpiMode & dpiGeoServer ) - setQueryItem( url, "FORMAT_OPTIONS", QString( "dpi:%1" ).arg( mDpi ) ); - } - - if ( mSettings.mImageMimeType == "image/x-jpegorpng" || - ( !mSettings.mImageMimeType.contains( "jpeg", Qt::CaseInsensitive ) && - !mSettings.mImageMimeType.contains( "jpg", Qt::CaseInsensitive ) ) ) - { - setQueryItem( url, "TRANSPARENT", "TRUE" ); // some servers giving error for 'true' (lowercase) - } - - int i = 0; - for ( int row = row0; row <= row1; row++ ) - { - for ( int col = col0; col <= col1; col++ ) - { - QgsRectangle bbox( tm->tileBBox( col, row ) ); - QString turl; - turl += url.toString(); - turl += QString( changeXY ? "&BBOX=%2,%1,%4,%3" : "&BBOX=%1,%2,%3,%4" ) - .arg( qgsDoubleToString( bbox.xMinimum() ), - qgsDoubleToString( bbox.yMinimum() ), - qgsDoubleToString( bbox.xMaximum() ), - qgsDoubleToString( bbox.yMaximum() ) ); - - QgsDebugMsg( QString( "tileRequest %1 %2/%3 (%4,%5): %6" ).arg( mTileReqNo ).arg( i++ ).arg( n ).arg( row ).arg( col ).arg( turl ) ); - requests << QgsWmsTiledImageDownloadHandler::TileRequest( turl, tm->tileRect( col, row ), i ); - } - } - } - break; + createTileRequestsWMSC( tm, tiles, requests ); + break; case WMTS: - { - if ( !getTileUrl().isNull() ) - { - // KVP - QUrl url( mSettings.mIgnoreGetMapUrl ? mSettings.mBaseUrl : getTileUrl() ); - - // compose static request arguments. - setQueryItem( url, "SERVICE", "WMTS" ); - setQueryItem( url, "REQUEST", "GetTile" ); - setQueryItem( url, "VERSION", mCaps.mCapabilities.version ); - setQueryItem( url, "LAYER", mSettings.mActiveSubLayers[0] ); - setQueryItem( url, "STYLE", mSettings.mActiveSubStyles[0] ); - setQueryItem( url, "FORMAT", mSettings.mImageMimeType ); - setQueryItem( url, "TILEMATRIXSET", mTileMatrixSet->identifier ); - setQueryItem( url, "TILEMATRIX", tm->identifier ); - - for ( QHash::const_iterator it = mSettings.mTileDimensionValues.constBegin(); it != mSettings.mTileDimensionValues.constEnd(); ++it ) - { - setQueryItem( url, it.key(), it.value() ); - } - - url.removeQueryItem( "TILEROW" ); - url.removeQueryItem( "TILECOL" ); - - int i = 0; - for ( int row = row0; row <= row1; row++ ) - { - for ( int col = col0; col <= col1; col++ ) - { - QString turl; - turl += url.toString(); - turl += QString( "&TILEROW=%1&TILECOL=%2" ).arg( row ).arg( col ); - - QgsDebugMsg( QString( "tileRequest %1 %2/%3 (%4,%5): %6" ).arg( mTileReqNo ).arg( i++ ).arg( n ).arg( row ).arg( col ).arg( turl ) ); - requests << QgsWmsTiledImageDownloadHandler::TileRequest( turl, tm->tileRect( col, row ), i ); - } - } - } - else - { - // REST - QString url = mTileLayer->getTileURLs[ mSettings.mImageMimeType ]; - - url.replace( "{layer}", mSettings.mActiveSubLayers[0], Qt::CaseInsensitive ); - url.replace( "{style}", mSettings.mActiveSubStyles[0], Qt::CaseInsensitive ); - url.replace( "{tilematrixset}", mTileMatrixSet->identifier, Qt::CaseInsensitive ); - url.replace( "{tilematrix}", tm->identifier, Qt::CaseInsensitive ); - - for ( QHash::const_iterator it = mSettings.mTileDimensionValues.constBegin(); it != mSettings.mTileDimensionValues.constEnd(); ++it ) - { - url.replace( "{" + it.key() + "}", it.value(), Qt::CaseInsensitive ); - } - - int i = 0; - for ( int row = row0; row <= row1; row++ ) - { - for ( int col = col0; col <= col1; col++ ) - { - QString turl( url ); - turl.replace( "{tilerow}", QString::number( row ), Qt::CaseInsensitive ); - turl.replace( "{tilecol}", QString::number( col ), Qt::CaseInsensitive ); - - if ( feedback && !feedback->preview_only ) - QgsDebugMsg( QString( "tileRequest %1 %2/%3 (%4,%5): %6" ).arg( mTileReqNo ).arg( i++ ).arg( n ).arg( row ).arg( col ).arg( turl ) ); - requests << QgsWmsTiledImageDownloadHandler::TileRequest( turl, tm->tileRect( col, row ), i ); - } - } - } - } - break; + createTileRequestsWMTS( tm, tiles, requests ); + break; case XYZ: - { - QString url = mSettings.mBaseUrl; - int z = tm->identifier.toInt(); - int i = 0; - for ( int row = row0; row <= row1; row++ ) - { - for ( int col = col0; col <= col1; col++ ) - { - QString turl( url ); - turl.replace( "{x}", QString::number( col ), Qt::CaseInsensitive ); - turl.replace( "{y}", QString::number( row ), Qt::CaseInsensitive ); - turl.replace( "{z}", QString::number( z ), Qt::CaseInsensitive ); - - if ( feedback && !feedback->preview_only ) - QgsDebugMsg( QString( "tileRequest %1 %2/%3 (%4,%5): %6" ).arg( mTileReqNo ).arg( i++ ).arg( n ).arg( row ).arg( col ).arg( turl ) ); - requests << QgsWmsTiledImageDownloadHandler::TileRequest( turl, tm->tileRect( col, row ), i ); - } - } - } - break; + createTileRequestsXYZ( tm, tiles, requests ); + break; default: QgsDebugMsg( QString( "unexpected tile mode %1" ).arg( mTileLayer->tileMode ) ); @@ -838,8 +645,8 @@ QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, i QTime t; t.start(); int memCached = 0, diskCached = 0; - QList requestsFinal; - Q_FOREACH ( const QgsWmsTiledImageDownloadHandler::TileRequest& r, requests ) + TileRequests requestsFinal; + Q_FOREACH ( const TileRequest& r, requests ) { QImage localImage; @@ -953,6 +760,220 @@ void QgsWmsProvider::readBlock( int bandNo, QgsRectangle const & viewExtent, in delete image; } +QUrl QgsWmsProvider::createRequestUrlWMS( const QgsRectangle& viewExtent, int pixelWidth, int pixelHeight ) +{ + // Calculate active layers that are also visible. + + bool changeXY = mCaps.shouldInvertAxisOrientation( mImageCrs ); + + QgsDebugMsg( "Active layer list of " + mSettings.mActiveSubLayers.join( ", " ) + + " and style list of " + mSettings.mActiveSubStyles.join( ", " ) ); + + QStringList visibleLayers = QStringList(); + QStringList visibleStyles = QStringList(); + + QStringList::const_iterator it2 = mSettings.mActiveSubStyles.constBegin(); + + for ( QStringList::const_iterator it = mSettings.mActiveSubLayers.constBegin(); + it != mSettings.mActiveSubLayers.constEnd(); + ++it ) + { + if ( mActiveSubLayerVisibility.constFind( *it ).value() ) + { + visibleLayers += *it; + visibleStyles += *it2; + } + + ++it2; + } + + QString layers = visibleLayers.join( "," ); + layers = layers.isNull() ? "" : layers; + QString styles = visibleStyles.join( "," ); + styles = styles.isNull() ? "" : styles; + + QgsDebugMsg( "Visible layer list of " + layers + " and style list of " + styles ); + + QString bbox = toParamValue( viewExtent, changeXY ); + + QUrl url( mSettings.mIgnoreGetMapUrl ? mSettings.mBaseUrl : getMapUrl() ); + setQueryItem( url, "SERVICE", "WMS" ); + setQueryItem( url, "VERSION", mCaps.mCapabilities.version ); + setQueryItem( url, "REQUEST", "GetMap" ); + setQueryItem( url, "BBOX", bbox ); + setSRSQueryItem( url ); + setQueryItem( url, "WIDTH", QString::number( pixelWidth ) ); + setQueryItem( url, "HEIGHT", QString::number( pixelHeight ) ); + setQueryItem( url, "LAYERS", layers ); + setQueryItem( url, "STYLES", styles ); + setFormatQueryItem( url ); + + if ( mDpi != -1 ) + { + if ( mSettings.mDpiMode & dpiQGIS ) + setQueryItem( url, "DPI", QString::number( mDpi ) ); + if ( mSettings.mDpiMode & dpiUMN ) + setQueryItem( url, "MAP_RESOLUTION", QString::number( mDpi ) ); + if ( mSettings.mDpiMode & dpiGeoServer ) + setQueryItem( url, "FORMAT_OPTIONS", QString( "dpi:%1" ).arg( mDpi ) ); + } + + //MH: jpeg does not support transparency and some servers complain if jpg and transparent=true + if ( mSettings.mImageMimeType == "image/x-jpegorpng" || + ( !mSettings.mImageMimeType.contains( "jpeg", Qt::CaseInsensitive ) && + !mSettings.mImageMimeType.contains( "jpg", Qt::CaseInsensitive ) ) ) + { + setQueryItem( url, "TRANSPARENT", "TRUE" ); // some servers giving error for 'true' (lowercase) + } + + QgsDebugMsg( QString( "getmap: %1" ).arg( url.toString() ) ); + return url; +} + + +void QgsWmsProvider::createTileRequestsWMSC( const QgsWmtsTileMatrix* tm, const QgsWmsProvider::TilePositions& tiles, QgsWmsProvider::TileRequests& requests ) +{ + bool changeXY = mCaps.shouldInvertAxisOrientation( mImageCrs ); + + // add WMS request + QUrl url( mSettings.mIgnoreGetMapUrl ? mSettings.mBaseUrl : getMapUrl() ); + setQueryItem( url, "SERVICE", "WMS" ); + setQueryItem( url, "VERSION", mCaps.mCapabilities.version ); + setQueryItem( url, "REQUEST", "GetMap" ); + setQueryItem( url, "LAYERS", mSettings.mActiveSubLayers.join( "," ) ); + setQueryItem( url, "STYLES", mSettings.mActiveSubStyles.join( "," ) ); + setQueryItem( url, "WIDTH", QString::number( tm->tileWidth ) ); + setQueryItem( url, "HEIGHT", QString::number( tm->tileHeight ) ); + setFormatQueryItem( url ); + + setSRSQueryItem( url ); + + if ( mSettings.mTiled ) + { + setQueryItem( url, "TILED", "true" ); + } + + if ( mDpi != -1 ) + { + if ( mSettings.mDpiMode & dpiQGIS ) + setQueryItem( url, "DPI", QString::number( mDpi ) ); + if ( mSettings.mDpiMode & dpiUMN ) + setQueryItem( url, "MAP_RESOLUTION", QString::number( mDpi ) ); + if ( mSettings.mDpiMode & dpiGeoServer ) + setQueryItem( url, "FORMAT_OPTIONS", QString( "dpi:%1" ).arg( mDpi ) ); + } + + if ( mSettings.mImageMimeType == "image/x-jpegorpng" || + ( !mSettings.mImageMimeType.contains( "jpeg", Qt::CaseInsensitive ) && + !mSettings.mImageMimeType.contains( "jpg", Qt::CaseInsensitive ) ) ) + { + setQueryItem( url, "TRANSPARENT", "TRUE" ); // some servers giving error for 'true' (lowercase) + } + + int i = 0; + Q_FOREACH ( const TilePosition& tile, tiles ) + { + QgsRectangle bbox( tm->tileBBox( tile.col, tile.row ) ); + QString turl; + turl += url.toString(); + turl += QString( changeXY ? "&BBOX=%2,%1,%4,%3" : "&BBOX=%1,%2,%3,%4" ) + .arg( qgsDoubleToString( bbox.xMinimum() ), + qgsDoubleToString( bbox.yMinimum() ), + qgsDoubleToString( bbox.xMaximum() ), + qgsDoubleToString( bbox.yMaximum() ) ); + + QgsDebugMsg( QString( "tileRequest %1 %2/%3 (%4,%5): %6" ).arg( mTileReqNo ).arg( i ).arg( tiles.count() ).arg( tile.row ).arg( tile.col ).arg( turl ) ); + requests << TileRequest( turl, tm->tileRect( tile.col, tile.row ), i ); + ++i; + } +} + + +void QgsWmsProvider::createTileRequestsWMTS( const QgsWmtsTileMatrix* tm, const QgsWmsProvider::TilePositions& tiles, QgsWmsProvider::TileRequests& requests ) +{ + if ( !getTileUrl().isNull() ) + { + // KVP + QUrl url( mSettings.mIgnoreGetMapUrl ? mSettings.mBaseUrl : getTileUrl() ); + + // compose static request arguments. + setQueryItem( url, "SERVICE", "WMTS" ); + setQueryItem( url, "REQUEST", "GetTile" ); + setQueryItem( url, "VERSION", mCaps.mCapabilities.version ); + setQueryItem( url, "LAYER", mSettings.mActiveSubLayers[0] ); + setQueryItem( url, "STYLE", mSettings.mActiveSubStyles[0] ); + setQueryItem( url, "FORMAT", mSettings.mImageMimeType ); + setQueryItem( url, "TILEMATRIXSET", mTileMatrixSet->identifier ); + setQueryItem( url, "TILEMATRIX", tm->identifier ); + + for ( QHash::const_iterator it = mSettings.mTileDimensionValues.constBegin(); it != mSettings.mTileDimensionValues.constEnd(); ++it ) + { + setQueryItem( url, it.key(), it.value() ); + } + + url.removeQueryItem( "TILEROW" ); + url.removeQueryItem( "TILECOL" ); + + int i = 0; + Q_FOREACH ( const TilePosition& tile, tiles ) + { + QString turl; + turl += url.toString(); + turl += QString( "&TILEROW=%1&TILECOL=%2" ).arg( tile.row ).arg( tile.col ); + + QgsDebugMsg( QString( "tileRequest %1 %2/%3 (%4,%5): %6" ).arg( mTileReqNo ).arg( i ).arg( tiles.count() ).arg( tile.row ).arg( tile.col ).arg( turl ) ); + requests << TileRequest( turl, tm->tileRect( tile.col, tile.row ), i ); + ++i; + } + } + else + { + // REST + QString url = mTileLayer->getTileURLs[ mSettings.mImageMimeType ]; + + url.replace( "{layer}", mSettings.mActiveSubLayers[0], Qt::CaseInsensitive ); + url.replace( "{style}", mSettings.mActiveSubStyles[0], Qt::CaseInsensitive ); + url.replace( "{tilematrixset}", mTileMatrixSet->identifier, Qt::CaseInsensitive ); + url.replace( "{tilematrix}", tm->identifier, Qt::CaseInsensitive ); + + for ( QHash::const_iterator it = mSettings.mTileDimensionValues.constBegin(); it != mSettings.mTileDimensionValues.constEnd(); ++it ) + { + url.replace( "{" + it.key() + "}", it.value(), Qt::CaseInsensitive ); + } + + int i = 0; + Q_FOREACH ( const TilePosition& tile, tiles ) + { + QString turl( url ); + turl.replace( "{tilerow}", QString::number( tile.row ), Qt::CaseInsensitive ); + turl.replace( "{tilecol}", QString::number( tile.col ), Qt::CaseInsensitive ); + + QgsDebugMsgLevel( QString( "tileRequest %1 %2/%3 (%4,%5): %6" ).arg( mTileReqNo ).arg( i ).arg( tiles.count() ).arg( tile.row ).arg( tile.col ).arg( turl ), 2 ); + requests << TileRequest( turl, tm->tileRect( tile.col, tile.row ), i ); + ++i; + } + } +} + + +void QgsWmsProvider::createTileRequestsXYZ( const QgsWmtsTileMatrix* tm, const QgsWmsProvider::TilePositions& tiles, QgsWmsProvider::TileRequests& requests ) +{ + int z = tm->identifier.toInt(); + QString url = mSettings.mBaseUrl; + int i = 0; + Q_FOREACH ( const TilePosition& tile, tiles ) + { + ++i; + QString turl( url ); + turl.replace( "{x}", QString::number( tile.col ), Qt::CaseInsensitive ); + turl.replace( "{y}", QString::number( tile.row ), Qt::CaseInsensitive ); + turl.replace( "{z}", QString::number( z ), Qt::CaseInsensitive ); + + QgsDebugMsgLevel( QString( "tileRequest %1 %2/%3 (%4,%5): %6" ).arg( mTileReqNo ).arg( i ).arg( tiles.count() ).arg( tile.row ).arg( tile.col ).arg( turl ), 2 ); + requests << TileRequest( turl, tm->tileRect( tile.col, tile.row ), i ); + } +} + bool QgsWmsProvider::retrieveServerCapabilities( bool forceRefresh ) { @@ -3498,7 +3519,7 @@ void QgsWmsImageDownloadHandler::cancelled() // ---------- -QgsWmsTiledImageDownloadHandler::QgsWmsTiledImageDownloadHandler( const QString& providerUri, const QgsWmsAuthorization& auth, int tileReqNo, const QList& requests, QImage* image, const QgsRectangle& viewExtent, bool smoothPixmapTransform, QgsRasterBlockFeedback* feedback ) +QgsWmsTiledImageDownloadHandler::QgsWmsTiledImageDownloadHandler( const QString& providerUri, const QgsWmsAuthorization& auth, int tileReqNo, const QgsWmsProvider::TileRequests& requests, QImage* image, const QgsRectangle& viewExtent, bool smoothPixmapTransform, QgsRasterBlockFeedback* feedback ) : mProviderUri( providerUri ) , mAuth( auth ) , mImage( image ) @@ -3518,7 +3539,7 @@ QgsWmsTiledImageDownloadHandler::QgsWmsTiledImageDownloadHandler( const QString& return; } - Q_FOREACH ( const TileRequest& r, requests ) + Q_FOREACH ( const QgsWmsProvider::TileRequest& r, requests ) { QNetworkRequest request( r.url ); auth.setAuthorization( request ); diff --git a/src/providers/wms/qgswmsprovider.h b/src/providers/wms/qgswmsprovider.h index 9a137faa260a..798e14662d51 100644 --- a/src/providers/wms/qgswmsprovider.h +++ b/src/providers/wms/qgswmsprovider.h @@ -348,6 +348,20 @@ class QgsWmsProvider : public QgsRasterDataProvider */ static QString prepareUri( QString uri ); + //! Helper struct for tile requests + struct TileRequest + { + TileRequest( const QUrl& u, const QRectF& r, int i ) + : url( u ) + , rect( r ) + , index( i ) + {} + QUrl url; + QRectF rect; + int index; + }; + typedef QList TileRequests; + signals: /** \brief emit a signal to notify of a progress event */ @@ -427,6 +441,20 @@ class QgsWmsProvider : public QgsRasterDataProvider private: + //! Tile identifier within a tile source + typedef struct TilePosition + { + TilePosition( int r, int c ): row( r ), col( c ) {} + int row; + int col; + } TilePosition; + typedef QList TilePositions; + + QUrl createRequestUrlWMS( const QgsRectangle& viewExtent, int pixelWidth, int pixelHeight ); + void createTileRequestsWMSC( const QgsWmtsTileMatrix* tm, const QgsWmsProvider::TilePositions& tiles, QgsWmsProvider::TileRequests& requests ); + void createTileRequestsWMTS( const QgsWmtsTileMatrix* tm, const QgsWmsProvider::TilePositions& tiles, QgsWmsProvider::TileRequests& requests ); + void createTileRequestsXYZ( const QgsWmtsTileMatrix* tm, const QgsWmsProvider::TilePositions& tiles, QgsWmsProvider::TileRequests& requests ); + /** Return the full url to request legend graphic * The visibleExtent isi only used if provider supports contextual * legends according to the QgsWmsSettings @@ -582,19 +610,7 @@ class QgsWmsTiledImageDownloadHandler : public QObject Q_OBJECT public: - struct TileRequest - { - TileRequest( const QUrl& u, const QRectF& r, int i ) - : url( u ) - , rect( r ) - , index( i ) - {} - QUrl url; - QRectF rect; - int index; - }; - - QgsWmsTiledImageDownloadHandler( const QString& providerUri, const QgsWmsAuthorization& auth, int reqNo, const QList& requests, QImage* image, const QgsRectangle& viewExtent, bool smoothPixmapTransform, QgsRasterBlockFeedback* feedback ); + QgsWmsTiledImageDownloadHandler( const QString& providerUri, const QgsWmsAuthorization& auth, int reqNo, const QgsWmsProvider::TileRequests& requests, QImage* image, const QgsRectangle& viewExtent, bool smoothPixmapTransform, QgsRasterBlockFeedback* feedback ); ~QgsWmsTiledImageDownloadHandler(); void downloadBlocking(); From 58e62ed7d679f69811887710bbaa276bbae15559 Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Mon, 5 Sep 2016 12:59:49 +0800 Subject: [PATCH 14/26] Fix loading of projects with XYZ tile layers --- src/core/qgsmaplayer.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/qgsmaplayer.cpp b/src/core/qgsmaplayer.cpp index c7da43cbde6a..e6c6038e9dd9 100644 --- a/src/core/qgsmaplayer.cpp +++ b/src/core/qgsmaplayer.cpp @@ -234,7 +234,9 @@ bool QgsMapLayer::readLayerXml( const QDomElement& layerElement ) // This is modified version of old QgsWmsProvider::parseUri // The new format has always params crs,format,layers,styles and that params // should not appear in old format url -> use them to identify version - if ( !mDataSource.contains( "crs=" ) && !mDataSource.contains( "format=" ) ) + // XYZ tile layers do not need to contain crs,format params, but they have type=xyz + if ( !mDataSource.contains( "type=" ) && + !mDataSource.contains( "crs=" ) && !mDataSource.contains( "format=" ) ) { QgsDebugMsg( "Old WMS URI format detected -> converting to new format" ); QgsDataSourceUri uri; From 9f351fa4a87c97d56ad1842eedbc75886a9f3d0a Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Mon, 5 Sep 2016 13:07:21 +0800 Subject: [PATCH 15/26] Add missing docs --- src/core/raster/qgsrasterlayerrenderer.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/core/raster/qgsrasterlayerrenderer.h b/src/core/raster/qgsrasterlayerrenderer.h index 70b9b4ca1206..3f992941904c 100644 --- a/src/core/raster/qgsrasterlayerrenderer.h +++ b/src/core/raster/qgsrasterlayerrenderer.h @@ -57,10 +57,15 @@ class QgsRasterLayerRenderer : public QgsMapLayerRenderer QgsRasterPipe* mPipe; QgsRenderContext& mContext; - //! Specific feedback class to provide preview of raster layer rendering. + /** \ingroup core + * Specific internal feedback class to provide preview of raster layer rendering. + * @note added in 3.0 + * @note not available in Python bindings + */ class Feedback : public QgsRasterBlockFeedback { public: + //! Create feedback object based on our layer renderer explicit Feedback( QgsRasterLayerRenderer* r ); //! when notified of new data in data provider it launches a preview draw of the raster From 2d9e72de1c392dcf44f26dfc18cbcd47f8c5e692 Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Mon, 5 Sep 2016 14:56:24 +0800 Subject: [PATCH 16/26] Fix doxygen warnings --- src/core/raster/qgsrasterprojector.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/core/raster/qgsrasterprojector.cpp b/src/core/raster/qgsrasterprojector.cpp index 37315926ad6b..508eb5fac874 100644 --- a/src/core/raster/qgsrasterprojector.cpp +++ b/src/core/raster/qgsrasterprojector.cpp @@ -64,6 +64,10 @@ Qgis::DataType QgsRasterProjector::dataType( int bandNo ) const return Qgis::UnknownDataType; } + +/// @cond PRIVATE + + void QgsRasterProjector::setCrs( const QgsCoordinateReferenceSystem & theSrcCRS, const QgsCoordinateReferenceSystem & theDestCRS, int srcDatumTransform, int destDatumTransform ) { mSrcCRS = theSrcCRS; @@ -726,6 +730,9 @@ bool ProjectorData::checkRows( const QgsCoordinateTransform& ct ) return true; } +/// @endcond + + QString QgsRasterProjector::precisionLabel( Precision precision ) { switch ( precision ) From beb5d0034f4992ac497e9eeac8b47145375181e9 Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Mon, 5 Sep 2016 19:21:37 +0800 Subject: [PATCH 17/26] Fix crashes in styling dock when dealing with WMTS / XYZ --- src/app/qgslayerstylingwidget.cpp | 42 +++++++++++-------- .../raster/qgsrastertransparencywidget.cpp | 7 ++++ 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/app/qgslayerstylingwidget.cpp b/src/app/qgslayerstylingwidget.cpp index beef6e65b3ac..70bbad9630fc 100644 --- a/src/app/qgslayerstylingwidget.cpp +++ b/src/app/qgslayerstylingwidget.cpp @@ -38,6 +38,7 @@ #include "qgsrenderer.h" #include "qgsrendererregistry.h" #include "qgsmaplayerregistry.h" +#include "qgsrasterdataprovider.h" #include "qgsrasterlayer.h" #include "qgsmaplayerconfigwidget.h" #include "qgsmaplayerstylemanagerwidget.h" @@ -171,10 +172,14 @@ void QgsLayerStylingWidget::setLayer( QgsMapLayer *layer ) transparencyItem->setToolTip( tr( "Transparency" ) ); transparencyItem->setData( Qt::UserRole, RasterTransparency ); mOptionsListWidget->addItem( transparencyItem ); - QListWidgetItem* histogramItem = new QListWidgetItem( QgsApplication::getThemeIcon( "propertyicons/histogram.png" ), QString() ); - histogramItem->setData( Qt::UserRole, RasterHistogram ); - mOptionsListWidget->addItem( histogramItem ); - histogramItem->setToolTip( tr( "Histogram" ) ); + + if ( static_cast( layer )->dataProvider()->capabilities() & QgsRasterDataProvider::Size ) + { + QListWidgetItem* histogramItem = new QListWidgetItem( QgsApplication::getThemeIcon( "propertyicons/histogram.png" ), QString() ); + histogramItem->setData( Qt::UserRole, RasterHistogram ); + mOptionsListWidget->addItem( histogramItem ); + histogramItem->setToolTip( tr( "Histogram" ) ); + } } Q_FOREACH ( QgsMapLayerConfigWidgetFactory* factory, mPageFactories ) @@ -392,21 +397,24 @@ void QgsLayerStylingWidget::updateCurrentWidgetLayer() } case 2: // Histogram { - if ( mRasterStyleWidget ) + if ( rlayer->dataProvider()->capabilities() & QgsRasterDataProvider::Size ) { - mRasterStyleWidget->deleteLater(); - delete mRasterStyleWidget; + if ( mRasterStyleWidget ) + { + mRasterStyleWidget->deleteLater(); + delete mRasterStyleWidget; + } + mRasterStyleWidget = new QgsRendererRasterPropertiesWidget( rlayer, mMapCanvas, mWidgetStack ); + mRasterStyleWidget->syncToLayer( rlayer ); + connect( mRasterStyleWidget, SIGNAL( widgetChanged() ), this, SLOT( autoApply() ) ); + + QgsRasterHistogramWidget* widget = new QgsRasterHistogramWidget( rlayer, mWidgetStack ); + connect( widget, SIGNAL( widgetChanged() ), this, SLOT( autoApply() ) ); + QString name = mRasterStyleWidget->currentRenderWidget()->renderer()->type(); + widget->setRendererWidget( name, mRasterStyleWidget->currentRenderWidget() ); + + mWidgetStack->addMainPanel( widget ); } - mRasterStyleWidget = new QgsRendererRasterPropertiesWidget( rlayer, mMapCanvas, mWidgetStack ); - mRasterStyleWidget->syncToLayer( rlayer ); - connect( mRasterStyleWidget, SIGNAL( widgetChanged() ), this, SLOT( autoApply() ) ); - - QgsRasterHistogramWidget* widget = new QgsRasterHistogramWidget( rlayer, mWidgetStack ); - connect( widget, SIGNAL( widgetChanged() ), this, SLOT( autoApply() ) ); - QString name = mRasterStyleWidget->currentRenderWidget()->renderer()->type(); - widget->setRendererWidget( name, mRasterStyleWidget->currentRenderWidget() ); - - mWidgetStack->addMainPanel( widget ); break; } default: diff --git a/src/gui/raster/qgsrastertransparencywidget.cpp b/src/gui/raster/qgsrastertransparencywidget.cpp index ea7d9aebc751..b139375c8a4f 100644 --- a/src/gui/raster/qgsrastertransparencywidget.cpp +++ b/src/gui/raster/qgsrastertransparencywidget.cpp @@ -76,6 +76,13 @@ void QgsRasterTransparencyWidget::syncToLayer() QgsRasterRenderer* renderer = mRasterLayer->renderer(); if ( provider ) { + if ( provider->dataType( 1 ) == Qgis::ARGB32 + || provider->dataType( 1 ) == Qgis::ARGB32_Premultiplied ) + { + gboxNoDataValue->setEnabled( false ); + gboxCustomTransparency->setEnabled( false ); + } + cboxTransparencyBand->addItem( tr( "None" ), -1 ); int nBands = provider->bandCount(); QString bandName; From 02a9211687d8bccc779c1b75b72f3f23384fee9f Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Fri, 9 Sep 2016 14:41:24 +0800 Subject: [PATCH 18/26] 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. --- src/providers/wms/qgswmscapabilities.cpp | 12 ++ src/providers/wms/qgswmscapabilities.h | 3 + src/providers/wms/qgswmsprovider.cpp | 238 +++++++++++++++++++---- src/providers/wms/qgswmsprovider.h | 29 ++- 4 files changed, 237 insertions(+), 45 deletions(-) diff --git a/src/providers/wms/qgswmscapabilities.cpp b/src/providers/wms/qgswmscapabilities.cpp index f1d1e0ade640..fc3e1a90381c 100644 --- a/src/providers/wms/qgswmscapabilities.cpp +++ b/src/providers/wms/qgswmscapabilities.cpp @@ -2151,3 +2151,15 @@ const QgsWmtsTileMatrix* QgsWmtsTileMatrixSet::findNearestResolution( double vre return &it.value(); } + +const QgsWmtsTileMatrix *QgsWmtsTileMatrixSet::findOtherResolution( double tres, int offset ) const +{ + QMap::const_iterator it = tileMatrices.constFind( tres ); + if ( it == tileMatrices.constEnd() ) + return nullptr; + it += offset; + if ( it == tileMatrices.constEnd() ) + return nullptr; + + return &it.value(); +} diff --git a/src/providers/wms/qgswmscapabilities.h b/src/providers/wms/qgswmscapabilities.h index 4e4d078d58a7..f760e5c35f20 100644 --- a/src/providers/wms/qgswmscapabilities.h +++ b/src/providers/wms/qgswmscapabilities.h @@ -350,6 +350,9 @@ struct QgsWmtsTileMatrixSet //! Returns closest tile resolution to the requested one. (resolution = width [map units] / with [pixels]) const QgsWmtsTileMatrix* findNearestResolution( double vres ) const; + + //! Return tile matrix for other near resolution from given tres (positive offset = lower resolution tiles) + const QgsWmtsTileMatrix* findOtherResolution( double tres, int offset ) const; }; enum QgsTileMode { WMTS, WMSC, XYZ }; diff --git a/src/providers/wms/qgswmsprovider.cpp b/src/providers/wms/qgswmsprovider.cpp index 4e16e8b643b1..f6e1bb1fa9a9 100644 --- a/src/providers/wms/qgswmsprovider.cpp +++ b/src/providers/wms/qgswmsprovider.cpp @@ -502,9 +502,143 @@ QImage *QgsWmsProvider::draw( QgsRectangle const &viewExtent, int pixelWidth, in } #include -static QCache sTileCache; +static QCache sTileCache( 256 ); static QMutex sTileCacheMutex; +static bool _fetchCachedTileImage( const QUrl& url, QImage& localImage ) +{ + QMutexLocker locker( &sTileCacheMutex ); + if ( QImage* i = sTileCache.object( url ) ) + { + localImage = *i; + return true; + } + else if ( QgsNetworkAccessManager::instance()->cache()->metaData( url ).isValid() ) + { + if ( QIODevice* data = QgsNetworkAccessManager::instance()->cache()->data( url ) ) + { + QByteArray imageData = data->readAll(); + delete data; + + localImage = QImage::fromData( imageData ); + + // cache it as well (mutex is already locked) + sTileCache.insert( url, new QImage( localImage ) ); + + return true; + } + } + return false; +} + +static bool _fuzzyContainsRect( const QRectF& r1, const QRectF& r2 ) +{ + double significantDigits = log10( qMax( r1.width(), r1.height() ) ); + double epsilon = exp10( significantDigits - 5 ); // floats have 6-9 significant digits + return r1.contains( r2.adjusted( epsilon, epsilon, -epsilon, -epsilon ) ); +} + +void QgsWmsProvider::fetchOtherResTiles( QgsTileMode tileMode, const QgsRectangle& viewExtent, int imageWidth, QList& missingRects, double tres, int resOffset, QList& otherResTiles ) +{ + const QgsWmtsTileMatrix* tmOther = mTileMatrixSet->findOtherResolution( tres, resOffset ); + if ( !tmOther ) + return; + + QSet tilesSet; + Q_FOREACH ( const QRectF& missingTileRect, missingRects ) + { + int c0, r0, c1, r1; + tmOther->viewExtentIntersection( QgsRectangle( missingTileRect ), nullptr, c0, r0, c1, r1 ); + + for ( int row = r0; row <= r1; row++ ) + { + for ( int col = c0; col <= c1; col++ ) + { + tilesSet << TilePosition( row, col ); + } + } + } + + // get URLs of tiles because their URLs are used as keys in the tile cache + TilePositions tiles = tilesSet.toList(); + TileRequests requests; + switch ( tileMode ) + { + case WMSC: + createTileRequestsWMSC( tmOther, tiles, requests ); + break; + + case WMTS: + createTileRequestsWMTS( tmOther, tiles, requests ); + break; + + case XYZ: + createTileRequestsXYZ( tmOther, tiles, requests ); + break; + } + + QList missingRectsToDelete; + Q_FOREACH ( const TileRequest& r, requests ) + { + QImage localImage; + if ( !_fetchCachedTileImage( r.url, localImage ) ) + continue; + + double cr = viewExtent.width() / imageWidth; + QRectF dst(( r.rect.left() - viewExtent.xMinimum() ) / cr, + ( viewExtent.yMaximum() - r.rect.bottom() ) / cr, + r.rect.width() / cr, + r.rect.height() / cr ); + otherResTiles << TileImage( dst, localImage ); + + // see if there are any missing rects that are completely covered by this tile + Q_FOREACH ( const QRectF& missingRect, missingRects ) + { + // we need to do a fuzzy "contains" check because the coordinates may not align perfectly + // due to numerical errors and/or transform of coords from double to floats + if ( _fuzzyContainsRect( r.rect, missingRect ) ) + { + missingRectsToDelete << missingRect; + } + } + } + + // remove all the rectangles we have completely covered by tiles from this resolution + // so we will not use tiles from multiple resolutions for one missing tile (to save time) + Q_FOREACH ( const QRectF& rectToDelete, missingRectsToDelete ) + { + missingRects.removeOne( rectToDelete ); + } + + QgsDebugMsg( QString( "Other resolution tiles: offset %1, res %2, missing rects %3, remaining rects %4, added tiles %5" ) + .arg( resOffset ) + .arg( tmOther->tres ) + .arg( missingRects.count() + missingRectsToDelete.count() ) + .arg( missingRects.count() ) + .arg( otherResTiles.count() ) ); +} + +uint qHash( const QgsWmsProvider::TilePosition& tp ) +{ + return ( uint ) tp.col + (( uint ) tp.row << 16 ); +} + +static void _drawDebugRect( QPainter& p, const QRectF& rect, const QColor& color ) +{ +#if 0 // good for debugging how tiles from various resolutions are used + QPainter::CompositionMode oldMode = p.compositionMode(); + p.setCompositionMode( QPainter::CompositionMode_SourceOver ); + QColor c = color; + c.setAlpha( 100 ); + p.fillRect( rect, QBrush( c, Qt::DiagCrossPattern ) ); + p.setCompositionMode( oldMode ); +#else + Q_UNUSED( p ); + Q_UNUSED( rect ); + Q_UNUSED( color ); +#endif +} + QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, int pixelHeight, QgsRasterBlockFeedback* feedback ) { QgsDebugMsg( "Entering." ); @@ -642,39 +776,16 @@ QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, i emit statusChanged( tr( "Getting tiles." ) ); + QList tileImages; // in the correct resolution + QList missing; // rectangles (in map coords) of missing tiles for this view + QTime t; t.start(); - int memCached = 0, diskCached = 0; TileRequests requestsFinal; Q_FOREACH ( const TileRequest& r, requests ) { QImage localImage; - - sTileCacheMutex.lock(); - if ( QImage* i = sTileCache.object( r.url ) ) - { - localImage = *i; - memCached++; - } - else if ( QgsNetworkAccessManager::instance()->cache()->metaData( r.url ).isValid() ) - { - if ( QIODevice* data = QgsNetworkAccessManager::instance()->cache()->data( r.url ) ) - { - QByteArray imageData = data->readAll(); - delete data; - - localImage = QImage::fromData( imageData ); - - // cache it as well (mutex is already locked) - sTileCache.insert( r.url, new QImage( localImage ) ); - - diskCached++; - } - } - sTileCacheMutex.unlock(); - - // draw the tile directly if possible - if ( !localImage.isNull() ) + if ( _fetchCachedTileImage( r.url, localImage ) ) { double cr = viewExtent.width() / image->width(); @@ -682,28 +793,82 @@ QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, i ( viewExtent.yMaximum() - r.rect.bottom() ) / cr, r.rect.width() / cr, r.rect.height() / cr ); - - QPainter p( image ); - if ( mSettings.mSmoothPixmapTransform ) - p.setRenderHint( QPainter::SmoothPixmapTransform, true ); - p.drawImage( dst, localImage ); + tileImages << TileImage( dst, localImage ); } else { + missing << r.rect; + // need to make a request requestsFinal << r; } } + int t0 = t.elapsed(); + + + // draw other res tiles if preview + QPainter p( image ); + if ( feedback && feedback->preview_only && missing.count() > 0 ) + { + // some tiles are still missing, so let's see if we have any cached tiles + // from lower or higher resolution available to give the user a bit of context + // while loading the right resolution + + p.setCompositionMode( QPainter::CompositionMode_Source ); + p.fillRect( image->rect(), QBrush( Qt::lightGray, Qt::CrossPattern ) ); + p.setRenderHint( QPainter::SmoothPixmapTransform, false ); // let's not waste time with bilinear filtering + + QList lowerResTiles, lowerResTiles2, higherResTiles; + // first we check lower resolution tiles: one level back, then two levels back (if there is still some are not covered), + // finally (in the worst case we use one level higher resolution tiles). This heuristic should give + // good overviews while not spending too much time drawing cached tiles from resolutions far away. + fetchOtherResTiles( tileMode, viewExtent, image->width(), missing, tm->tres, 1, lowerResTiles ); + fetchOtherResTiles( tileMode, viewExtent, image->width(), missing, tm->tres, 2, lowerResTiles2 ); + fetchOtherResTiles( tileMode, viewExtent, image->width(), missing, tm->tres, -1, higherResTiles ); + + // draw the cached tiles lowest to highest resolution + Q_FOREACH ( const TileImage& ti, lowerResTiles2 ) + { + p.drawImage( ti.rect, ti.img ); + _drawDebugRect( p, ti.rect, Qt::blue ); + } + Q_FOREACH ( const TileImage& ti, lowerResTiles ) + { + p.drawImage( ti.rect, ti.img ); + _drawDebugRect( p, ti.rect, Qt::yellow ); + } + Q_FOREACH ( const TileImage& ti, higherResTiles ) + { + p.drawImage( ti.rect, ti.img ); + _drawDebugRect( p, ti.rect, Qt::red ); + } + } + + int t1 = t.elapsed() - t0; + + // draw composite in this resolution + Q_FOREACH ( const TileImage& ti, tileImages ) + { + if ( mSettings.mSmoothPixmapTransform ) + p.setRenderHint( QPainter::SmoothPixmapTransform, true ); + p.drawImage( ti.rect, ti.img ); + + if ( feedback && feedback->preview_only ) + _drawDebugRect( p, ti.rect, Qt::green ); + } + p.end(); + + int t2 = t.elapsed() - t1; if ( feedback && feedback->preview_only ) { - qDebug( "PREVIEW - MEM CACHED: %d / DISK CACHED: %d / MISSING: %d", memCached, diskCached, requests.count() - memCached - diskCached ); - qDebug( "PREVIEW - SPENT IN WMTS PROVIDER: %d ms", t.elapsed() ); + qDebug( "PREVIEW - CACHED: %d / MISSING: %d", tileImages.count(), requests.count() - tileImages.count() ); + qDebug( "PREVIEW - TIME: this res %d ms | other res %d ms | TOTAL %d ms", t0 + t2, t1, t0 + t1 + t2 ); } else if ( !requestsFinal.isEmpty() ) { // let the feedback object know about the tiles we have already - if ( feedback && memCached + diskCached > 0 ) + if ( feedback && feedback->render_partial_output ) feedback->onNewData(); // order tile requests according to the distance from view center @@ -715,6 +880,7 @@ QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, i handler.downloadBlocking(); } + qDebug( "TILE CACHE total: %d / %d ", sTileCache.totalCost(), sTileCache.maxCost() ); #if 0 const QgsWmsStatistics::Stat& stat = QgsWmsStatistics::statForUri( dataSourceUri() ); diff --git a/src/providers/wms/qgswmsprovider.h b/src/providers/wms/qgswmsprovider.h index 798e14662d51..954277e3caa9 100644 --- a/src/providers/wms/qgswmsprovider.h +++ b/src/providers/wms/qgswmsprovider.h @@ -362,6 +362,16 @@ class QgsWmsProvider : public QgsRasterDataProvider }; typedef QList TileRequests; + //! Tile identifier within a tile source + typedef struct TilePosition + { + TilePosition( int r, int c ): row( r ), col( c ) {} + bool operator==( const TilePosition& other ) const { return row == other.row && col == other.col; } + int row; + int col; + } TilePosition; + typedef QList TilePositions; + signals: /** \brief emit a signal to notify of a progress event */ @@ -441,20 +451,21 @@ class QgsWmsProvider : public QgsRasterDataProvider private: - //! Tile identifier within a tile source - typedef struct TilePosition - { - TilePosition( int r, int c ): row( r ), col( c ) {} - int row; - int col; - } TilePosition; - typedef QList TilePositions; - QUrl createRequestUrlWMS( const QgsRectangle& viewExtent, int pixelWidth, int pixelHeight ); void createTileRequestsWMSC( const QgsWmtsTileMatrix* tm, const QgsWmsProvider::TilePositions& tiles, QgsWmsProvider::TileRequests& requests ); void createTileRequestsWMTS( const QgsWmtsTileMatrix* tm, const QgsWmsProvider::TilePositions& tiles, QgsWmsProvider::TileRequests& requests ); void createTileRequestsXYZ( const QgsWmtsTileMatrix* tm, const QgsWmsProvider::TilePositions& tiles, QgsWmsProvider::TileRequests& requests ); + //! Helper structure to store a cached tile image with its rectangle + typedef struct TileImage + { + TileImage( QRectF r, QImage i ): rect( r ), img( i ) {} + QRectF rect; //!< destination rectangle for a tile (in screen coordinates) + QImage img; //!< cached tile to be drawn + } TileImage; + //! Get tiles from a different resolution to cover the missing areas + void fetchOtherResTiles( QgsTileMode tileMode, const QgsRectangle& viewExtent, int imageWidth, QList& missing, double tres, int resOffset, QList &otherResTiles ); + /** Return the full url to request legend graphic * The visibleExtent isi only used if provider supports contextual * legends according to the QgsWmsSettings From 267f2633515dc61d856a6a012cbd07a38207acc6 Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Fri, 9 Sep 2016 15:31:10 +0800 Subject: [PATCH 19/26] Move tile cache to a new file --- src/providers/wms/CMakeLists.txt | 1 + src/providers/wms/qgstilecache.cpp | 57 ++++++++++++++++++++++++++++ src/providers/wms/qgstilecache.h | 56 +++++++++++++++++++++++++++ src/providers/wms/qgswmsprovider.cpp | 40 +++---------------- 4 files changed, 119 insertions(+), 35 deletions(-) create mode 100644 src/providers/wms/qgstilecache.cpp create mode 100644 src/providers/wms/qgstilecache.h diff --git a/src/providers/wms/CMakeLists.txt b/src/providers/wms/CMakeLists.txt index f4ec13c4d978..7a57c85773d9 100644 --- a/src/providers/wms/CMakeLists.txt +++ b/src/providers/wms/CMakeLists.txt @@ -9,6 +9,7 @@ SET (WMS_SRCS qgswmssourceselect.cpp qgswmsconnection.cpp qgswmsdataitems.cpp + qgstilecache.cpp qgstilescalewidget.cpp qgswmtsdimensions.cpp qgsxyzconnection.cpp diff --git a/src/providers/wms/qgstilecache.cpp b/src/providers/wms/qgstilecache.cpp new file mode 100644 index 000000000000..2cc94efc42c3 --- /dev/null +++ b/src/providers/wms/qgstilecache.cpp @@ -0,0 +1,57 @@ +/*************************************************************************** + qgstilecache.h + -------------------------------------- + Date : September 2016 + Copyright : (C) 2016 by Martin Dobias + Email : wonder dot sk at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgstilecache.h" + +#include "qgsnetworkaccessmanager.h" + +#include +#include + +QCache QgsTileCache::sTileCache( 256 ); +QMutex QgsTileCache::sTileCacheMutex; + + +void QgsTileCache::insertTile( const QUrl& url, const QImage& image ) +{ + QMutexLocker locker( &sTileCacheMutex ); + sTileCache.insert( url, new QImage( image ) ); +} + +bool QgsTileCache::tile( const QUrl& url, QImage& image ) +{ + QMutexLocker locker( &sTileCacheMutex ); + if ( QImage* i = sTileCache.object( url ) ) + { + image = *i; + return true; + } + else if ( QgsNetworkAccessManager::instance()->cache()->metaData( url ).isValid() ) + { + if ( QIODevice* data = QgsNetworkAccessManager::instance()->cache()->data( url ) ) + { + QByteArray imageData = data->readAll(); + delete data; + + image = QImage::fromData( imageData ); + + // cache it as well (mutex is already locked) + sTileCache.insert( url, new QImage( image ) ); + + return true; + } + } + return false; +} diff --git a/src/providers/wms/qgstilecache.h b/src/providers/wms/qgstilecache.h new file mode 100644 index 000000000000..39e5496baef4 --- /dev/null +++ b/src/providers/wms/qgstilecache.h @@ -0,0 +1,56 @@ +/*************************************************************************** + qgstilecache.h + -------------------------------------- + Date : September 2016 + Copyright : (C) 2016 by Martin Dobias + Email : wonder dot sk at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSTILECACHE_H +#define QGSTILECACHE_H + + +#include +#include + +class QImage; +class QUrl; + +/** A simple tile cache implementation. Tiles are cached according to their URL. + * There is a small in-memory cache and a secondary caching in the local disk. + * The in-memory cache is there to save CPU time otherwise wasted to read and + * uncompress data saved on the disk. + * + * The class is thread safe (its methods can be called from any thread). + */ +class QgsTileCache +{ + public: + + //! Add a tile image with given URL to the cache + static void insertTile( const QUrl& url, const QImage& image ); + + //! Try to access a tile and load it into "image" argument + //! @returns true if the tile exists in the cache + static bool tile( const QUrl& url, QImage& image ); + + //! how many tiles are stored in the in-memory cache + static int totalCost() { return sTileCache.totalCost(); } + //! how many tiles can be stored in the in-memory cache + static int maxCost() { return sTileCache.maxCost(); } + + private: + //! in-memory cache + static QCache sTileCache; + //! mutex to protect the in-memory cache + static QMutex sTileCacheMutex; +}; + +#endif // QGSTILECACHE_H diff --git a/src/providers/wms/qgswmsprovider.cpp b/src/providers/wms/qgswmsprovider.cpp index f6e1bb1fa9a9..fb75e085e521 100644 --- a/src/providers/wms/qgswmsprovider.cpp +++ b/src/providers/wms/qgswmsprovider.cpp @@ -40,6 +40,7 @@ #include "qgsmessagelog.h" #include "qgsnetworkaccessmanager.h" #include "qgsnetworkreplyparser.h" +#include "qgstilecache.h" #include "qgsgml.h" #include "qgsgmlschema.h" #include "qgswmscapabilities.h" @@ -501,35 +502,6 @@ QImage *QgsWmsProvider::draw( QgsRectangle const &viewExtent, int pixelWidth, in return draw( viewExtent, pixelWidth, pixelHeight, nullptr ); } -#include -static QCache sTileCache( 256 ); -static QMutex sTileCacheMutex; - -static bool _fetchCachedTileImage( const QUrl& url, QImage& localImage ) -{ - QMutexLocker locker( &sTileCacheMutex ); - if ( QImage* i = sTileCache.object( url ) ) - { - localImage = *i; - return true; - } - else if ( QgsNetworkAccessManager::instance()->cache()->metaData( url ).isValid() ) - { - if ( QIODevice* data = QgsNetworkAccessManager::instance()->cache()->data( url ) ) - { - QByteArray imageData = data->readAll(); - delete data; - - localImage = QImage::fromData( imageData ); - - // cache it as well (mutex is already locked) - sTileCache.insert( url, new QImage( localImage ) ); - - return true; - } - } - return false; -} static bool _fuzzyContainsRect( const QRectF& r1, const QRectF& r2 ) { @@ -581,7 +553,7 @@ void QgsWmsProvider::fetchOtherResTiles( QgsTileMode tileMode, const QgsRectangl Q_FOREACH ( const TileRequest& r, requests ) { QImage localImage; - if ( !_fetchCachedTileImage( r.url, localImage ) ) + if ( ! QgsTileCache::tile( r.url, localImage ) ) continue; double cr = viewExtent.width() / imageWidth; @@ -785,7 +757,7 @@ QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, i Q_FOREACH ( const TileRequest& r, requests ) { QImage localImage; - if ( _fetchCachedTileImage( r.url, localImage ) ) + if ( QgsTileCache::tile( r.url, localImage ) ) { double cr = viewExtent.width() / image->width(); @@ -880,7 +852,7 @@ QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, i handler.downloadBlocking(); } - qDebug( "TILE CACHE total: %d / %d ", sTileCache.totalCost(), sTileCache.maxCost() ); + qDebug( "TILE CACHE total: %d / %d ", QgsTileCache::totalCost(), QgsTileCache::maxCost() ); #if 0 const QgsWmsStatistics::Stat& stat = QgsWmsStatistics::statForUri( dataSourceUri() ); @@ -3909,9 +3881,7 @@ void QgsWmsTiledImageDownloadHandler::tileReplyFinished() .arg( r.width() ).arg( r.height() ) ); #endif - sTileCacheMutex.lock(); - sTileCache.insert( reply->url(), new QImage( myLocalImage ) ); - sTileCacheMutex.unlock(); + QgsTileCache::insertTile( reply->url(), myLocalImage ); if ( mFeedback ) mFeedback->onNewData(); From f6a2a5e7444dc3b500a2fbdc844ae83ecbd3c23c Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Fri, 9 Sep 2016 16:02:18 +0800 Subject: [PATCH 20/26] Clean up QgsRasterBlockFeedback class --- python/core/raster/qgsrasterinterface.sip | 27 ++++++++++------ src/core/raster/qgsrasterdrawer.cpp | 2 +- src/core/raster/qgsrasterinterface.h | 36 ++++++++++++++++------ src/core/raster/qgsrasterlayerrenderer.cpp | 8 ++--- src/providers/wms/qgswmsprovider.cpp | 10 +++--- 5 files changed, 54 insertions(+), 29 deletions(-) diff --git a/python/core/raster/qgsrasterinterface.sip b/python/core/raster/qgsrasterinterface.sip index 5d935a9caf32..895af7f49c9b 100644 --- a/python/core/raster/qgsrasterinterface.sip +++ b/python/core/raster/qgsrasterinterface.sip @@ -11,19 +11,28 @@ class QgsRasterBlockFeedback : QgsFeedback %End public: - //! construct a new raster block feedback object + //! Construct a new raster block feedback object QgsRasterBlockFeedback( QObject* parent = nullptr ); - //! whether the raster provider should return only data that are already available - //! without waiting for full result - bool preview_only; - - //! whether our painter is drawing to a temporary image used just by this layer - bool render_partial_output; - - //! may be emitted by raster data provider to indicate that some partial data are available + //! May be emitted by raster data provider to indicate that some partial data are available //! and a new preview image may be produced virtual void onNewData(); + + //! Whether the raster provider should return only data that are already available + //! without waiting for full result. By default this flag is not enabled. + //! @see setPreviewOnly() + bool isPreviewOnly() const; + //! set flag whether the block request is for preview purposes only + //! @see isPreviewOnly() + void setPreviewOnly( bool preview ); + + //! Whether our painter is drawing to a temporary image used just by this layer + //! @see setRenderPartialOutput() + bool renderPartialOutput() const; + //! Set whether our painter is drawing to a temporary image used just by this layer + //! @see renderPartialOutput() + void setRenderPartialOutput( bool enable ); + }; diff --git a/src/core/raster/qgsrasterdrawer.cpp b/src/core/raster/qgsrasterdrawer.cpp index 06d32e5ca22f..addd0b2e0e01 100644 --- a/src/core/raster/qgsrasterdrawer.cpp +++ b/src/core/raster/qgsrasterdrawer.cpp @@ -88,7 +88,7 @@ void QgsRasterDrawer::draw( QPainter* p, QgsRasterViewPort* viewPort, const QgsM } } - if ( feedback && feedback->render_partial_output ) + if ( feedback && feedback->renderPartialOutput() ) { // there could have been partial preview written before // so overwrite anything with the resulting image. diff --git a/src/core/raster/qgsrasterinterface.h b/src/core/raster/qgsrasterinterface.h index 931b0ff88095..d1f598ba78cd 100644 --- a/src/core/raster/qgsrasterinterface.h +++ b/src/core/raster/qgsrasterinterface.h @@ -37,19 +37,35 @@ class CORE_EXPORT QgsRasterBlockFeedback : public QgsFeedback { public: - //! construct a new raster block feedback object - QgsRasterBlockFeedback( QObject* parent = nullptr ) : QgsFeedback( parent ), preview_only( false ), render_partial_output( false ) {} + //! Construct a new raster block feedback object + QgsRasterBlockFeedback( QObject* parent = nullptr ) : QgsFeedback( parent ), mPreviewOnly( false ), mRenderPartialOutput( false ) {} - //! whether the raster provider should return only data that are already available - //! without waiting for full result - bool preview_only; - - //! whether our painter is drawing to a temporary image used just by this layer - bool render_partial_output; - - //! may be emitted by raster data provider to indicate that some partial data are available + //! May be emitted by raster data provider to indicate that some partial data are available //! and a new preview image may be produced virtual void onNewData() {} + + //! Whether the raster provider should return only data that are already available + //! without waiting for full result. By default this flag is not enabled. + //! @see setPreviewOnly() + bool isPreviewOnly() const { return mPreviewOnly; } + //! set flag whether the block request is for preview purposes only + //! @see isPreviewOnly() + void setPreviewOnly( bool preview ) { mPreviewOnly = preview; } + + //! Whether our painter is drawing to a temporary image used just by this layer + //! @see setRenderPartialOutput() + bool renderPartialOutput() const { return mRenderPartialOutput; } + //! Set whether our painter is drawing to a temporary image used just by this layer + //! @see renderPartialOutput() + void setRenderPartialOutput( bool enable ) { mRenderPartialOutput = enable; } + + private: + //! Whether the raster provider should return only data that are already available + //! without waiting for full result + bool mPreviewOnly; + + //! Whether our painter is drawing to a temporary image used just by this layer + bool mRenderPartialOutput; }; diff --git a/src/core/raster/qgsrasterlayerrenderer.cpp b/src/core/raster/qgsrasterlayerrenderer.cpp index 1a1643302849..aa9bae16c6dd 100644 --- a/src/core/raster/qgsrasterlayerrenderer.cpp +++ b/src/core/raster/qgsrasterlayerrenderer.cpp @@ -231,14 +231,14 @@ QgsRasterLayerRenderer::Feedback::Feedback( QgsRasterLayerRenderer *r ) : mR( r ) , mMinimalPreviewInterval( 250 ) { - render_partial_output = r->mContext.testFlag( QgsRenderContext::RenderPartialOutput ); + setRenderPartialOutput( r->mContext.testFlag( QgsRenderContext::RenderPartialOutput ) ); } void QgsRasterLayerRenderer::Feedback::onNewData() { qDebug( "\nGOT NEW DATA!\n" ); - if ( !render_partial_output ) + if ( !renderPartialOutput() ) return; // we were not asked for partial renders and we may not have a temporary image for overwriting... // update only once upon a time @@ -252,8 +252,8 @@ void QgsRasterLayerRenderer::Feedback::onNewData() QTime t; t.start(); QgsRasterBlockFeedback feedback; - feedback.preview_only = true; - feedback.render_partial_output = true; + feedback.setPreviewOnly( true ); + feedback.setRenderPartialOutput( true ); QgsRasterIterator iterator( mR->mPipe->last() ); QgsRasterDrawer drawer( &iterator ); drawer.draw( mR->mPainter, mR->mRasterViewPort, mR->mMapToPixel, &feedback ); diff --git a/src/providers/wms/qgswmsprovider.cpp b/src/providers/wms/qgswmsprovider.cpp index fb75e085e521..f85f153e4dee 100644 --- a/src/providers/wms/qgswmsprovider.cpp +++ b/src/providers/wms/qgswmsprovider.cpp @@ -780,7 +780,7 @@ QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, i // draw other res tiles if preview QPainter p( image ); - if ( feedback && feedback->preview_only && missing.count() > 0 ) + if ( feedback && feedback->isPreviewOnly() && missing.count() > 0 ) { // some tiles are still missing, so let's see if we have any cached tiles // from lower or higher resolution available to give the user a bit of context @@ -825,14 +825,14 @@ QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, i p.setRenderHint( QPainter::SmoothPixmapTransform, true ); p.drawImage( ti.rect, ti.img ); - if ( feedback && feedback->preview_only ) + if ( feedback && feedback->isPreviewOnly() ) _drawDebugRect( p, ti.rect, Qt::green ); } p.end(); int t2 = t.elapsed() - t1; - if ( feedback && feedback->preview_only ) + if ( feedback && feedback->isPreviewOnly() ) { qDebug( "PREVIEW - CACHED: %d / MISSING: %d", tileImages.count(), requests.count() - tileImages.count() ); qDebug( "PREVIEW - TIME: this res %d ms | other res %d ms | TOTAL %d ms", t0 + t2, t1, t0 + t1 + t2 ); @@ -840,7 +840,7 @@ QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, i else if ( !requestsFinal.isEmpty() ) { // let the feedback object know about the tiles we have already - if ( feedback && feedback->render_partial_output ) + if ( feedback && feedback->renderPartialOutput() ) feedback->onNewData(); // order tile requests according to the distance from view center @@ -3908,7 +3908,7 @@ void QgsWmsTiledImageDownloadHandler::tileReplyFinished() } else { - if ( !( mFeedback && mFeedback->preview_only ) ) + if ( !( mFeedback && mFeedback->isPreviewOnly() ) ) { // report any errors except for the one we have caused by cancelling the request if ( reply->error() != QNetworkReply::OperationCanceledError ) From 04b2239dde395d521a09d1f8da748b4484fc0001 Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Fri, 9 Sep 2016 16:35:23 +0800 Subject: [PATCH 21/26] Use QgsDebugMsg instead of qDebug --- src/core/raster/qgsrasterlayerrenderer.cpp | 6 ++---- src/providers/wms/qgswmsprovider.cpp | 6 +++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/core/raster/qgsrasterlayerrenderer.cpp b/src/core/raster/qgsrasterlayerrenderer.cpp index aa9bae16c6dd..01280a8f0b2a 100644 --- a/src/core/raster/qgsrasterlayerrenderer.cpp +++ b/src/core/raster/qgsrasterlayerrenderer.cpp @@ -236,8 +236,6 @@ QgsRasterLayerRenderer::Feedback::Feedback( QgsRasterLayerRenderer *r ) void QgsRasterLayerRenderer::Feedback::onNewData() { - qDebug( "\nGOT NEW DATA!\n" ); - if ( !renderPartialOutput() ) return; // we were not asked for partial renders and we may not have a temporary image for overwriting... @@ -248,7 +246,7 @@ void QgsRasterLayerRenderer::Feedback::onNewData() // TODO: update only the area that got new data - qDebug( "new raster preview! %d", mLastPreview.msecsTo( QTime::currentTime() ) ); + QgsDebugMsg( QString( "new raster preview! %1" ).arg( mLastPreview.msecsTo( QTime::currentTime() ) ) ); QTime t; t.start(); QgsRasterBlockFeedback feedback; @@ -257,6 +255,6 @@ void QgsRasterLayerRenderer::Feedback::onNewData() QgsRasterIterator iterator( mR->mPipe->last() ); QgsRasterDrawer drawer( &iterator ); drawer.draw( mR->mPainter, mR->mRasterViewPort, mR->mMapToPixel, &feedback ); - qDebug( "PREVIEW TOOK %d ms", t.elapsed() ); + QgsDebugMsg( QString( "total raster preview time: %1 ms" ).arg( t.elapsed() ) ); mLastPreview = QTime::currentTime(); } diff --git a/src/providers/wms/qgswmsprovider.cpp b/src/providers/wms/qgswmsprovider.cpp index f85f153e4dee..ab92896f3193 100644 --- a/src/providers/wms/qgswmsprovider.cpp +++ b/src/providers/wms/qgswmsprovider.cpp @@ -834,8 +834,8 @@ QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, i if ( feedback && feedback->isPreviewOnly() ) { - qDebug( "PREVIEW - CACHED: %d / MISSING: %d", tileImages.count(), requests.count() - tileImages.count() ); - qDebug( "PREVIEW - TIME: this res %d ms | other res %d ms | TOTAL %d ms", t0 + t2, t1, t0 + t1 + t2 ); + QgsDebugMsg( QString( "PREVIEW - CACHED: %1 / MISSING: %2" ).arg( tileImages.count() ).arg( requests.count() - tileImages.count() ) ); + QgsDebugMsg( QString( "PREVIEW - TIME: this res %1 ms | other res %2 ms | TOTAL %3 ms" ).arg( t0 + t2 ).arg( t1 ).arg( t0 + t1 + t2 ) ); } else if ( !requestsFinal.isEmpty() ) { @@ -852,7 +852,7 @@ QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, i handler.downloadBlocking(); } - qDebug( "TILE CACHE total: %d / %d ", QgsTileCache::totalCost(), QgsTileCache::maxCost() ); + QgsDebugMsg( QString( "TILE CACHE total: %1 / %2" ).arg( QgsTileCache::totalCost() ).arg( QgsTileCache::maxCost() ) ); #if 0 const QgsWmsStatistics::Stat& stat = QgsWmsStatistics::statForUri( dataSourceUri() ); From b025e3a33696b6fc7d2097c9255173b212197351 Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Fri, 9 Sep 2016 17:01:02 +0800 Subject: [PATCH 22/26] Add missing SIP bits --- python/core/qgsmapsettings.sip | 3 ++- python/core/qgsrendercontext.sip | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/python/core/qgsmapsettings.sip b/python/core/qgsmapsettings.sip index 3ad695653b33..b98c530b2559 100644 --- a/python/core/qgsmapsettings.sip +++ b/python/core/qgsmapsettings.sip @@ -116,7 +116,8 @@ class QgsMapSettings UseRenderingOptimization, //!< Enable vector simplification and other rendering optimizations DrawSelection, //!< Whether vector selections should be shown in the rendered map DrawSymbolBounds, //!< Draw bounds of symbols (for debugging/testing) - RenderMapTile //!< Draw map such that there are no problems between adjacent tiles + RenderMapTile, //!< Draw map such that there are no problems between adjacent tiles + RenderPartialOutput, //!< Whether to make extra effort to update map image with partially rendered layers (better for interactive map canvas). Added in QGIS 3.0 }; typedef QFlags Flags; diff --git a/python/core/qgsrendercontext.sip b/python/core/qgsrendercontext.sip index 7f588dcf0990..510e142403d3 100644 --- a/python/core/qgsrendercontext.sip +++ b/python/core/qgsrendercontext.sip @@ -23,6 +23,7 @@ class QgsRenderContext DrawSymbolBounds, //!< Draw bounds of symbols (for debugging/testing) RenderMapTile, //!< Draw map such that there are no problems between adjacent tiles Antialiasing, //!< Use antialiasing while drawing + RenderPartialOutput, //!< Whether to make extra effort to update map image with partially rendered layers (better for interactive map canvas). Added in QGIS 3.0 }; typedef QFlags Flags; From 03671eb29d021e47630176f43a7527949ea52434 Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Fri, 9 Sep 2016 17:29:57 +0800 Subject: [PATCH 23/26] Fix a warning in release build --- src/providers/wms/qgswmsprovider.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/providers/wms/qgswmsprovider.cpp b/src/providers/wms/qgswmsprovider.cpp index ab92896f3193..434153afa918 100644 --- a/src/providers/wms/qgswmsprovider.cpp +++ b/src/providers/wms/qgswmsprovider.cpp @@ -831,6 +831,7 @@ QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, i p.end(); int t2 = t.elapsed() - t1; + Q_UNUSED( t2 ); // only used in debug build if ( feedback && feedback->isPreviewOnly() ) { From 4fa3191a85a5702db262d001a15f5cc0a5a2b4d5 Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Mon, 12 Sep 2016 23:52:07 +0800 Subject: [PATCH 24/26] Fix tile previews with Qt5 --- src/core/raster/qgsrasterlayerrenderer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/raster/qgsrasterlayerrenderer.cpp b/src/core/raster/qgsrasterlayerrenderer.cpp index 01280a8f0b2a..a120fc8bbe5c 100644 --- a/src/core/raster/qgsrasterlayerrenderer.cpp +++ b/src/core/raster/qgsrasterlayerrenderer.cpp @@ -241,7 +241,7 @@ void QgsRasterLayerRenderer::Feedback::onNewData() // update only once upon a time // (preview itself takes some time) - if ( mLastPreview.msecsTo( QTime::currentTime() ) < mMinimalPreviewInterval ) + if ( mLastPreview.isValid() && mLastPreview.msecsTo( QTime::currentTime() ) < mMinimalPreviewInterval ) return; // TODO: update only the area that got new data From 02599356a09a69b7c1f49851d3f424fcfa4a5651 Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Tue, 13 Sep 2016 00:37:01 +0800 Subject: [PATCH 25/26] Fix crash on QMap iteration in Qt5 --- src/providers/wms/qgswmscapabilities.cpp | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/providers/wms/qgswmscapabilities.cpp b/src/providers/wms/qgswmscapabilities.cpp index fc3e1a90381c..c1f8aca01c61 100644 --- a/src/providers/wms/qgswmscapabilities.cpp +++ b/src/providers/wms/qgswmscapabilities.cpp @@ -2157,9 +2157,25 @@ const QgsWmtsTileMatrix *QgsWmtsTileMatrixSet::findOtherResolution( double tres, QMap::const_iterator it = tileMatrices.constFind( tres ); if ( it == tileMatrices.constEnd() ) return nullptr; - it += offset; - if ( it == tileMatrices.constEnd() ) - return nullptr; + while ( 1 ) + { + if ( offset > 0 ) + { + ++it; + --offset; + } + else if ( offset < 0 ) + { + if ( it == tileMatrices.constBegin() ) + return nullptr; + --it; + ++offset; + } + else + break; + if ( it == tileMatrices.constEnd() ) + return nullptr; + } return &it.value(); } From adc88f247a4f5087a0bdb9aba8cf277ee6d50be3 Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Tue, 13 Sep 2016 14:38:59 +0800 Subject: [PATCH 26/26] Bump up the tile request limit in WMS provider Max. 100 tiles is just too low. The max request area is currently 2000x2000 pixels which boils down to approx 8x8 tiles of size 256x256, therefore max 64 tiles. However this is the ideal case if the view scale matches the scale of tiles. If the map view scale is approx in between two levels, we may need 12x12 tiles to serve the request, so bumping the maximum to 256 should be enough. --- src/providers/wms/qgswmsprovider.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/providers/wms/qgswmsprovider.cpp b/src/providers/wms/qgswmsprovider.cpp index 434153afa918..2ba7414f82fd 100644 --- a/src/providers/wms/qgswmsprovider.cpp +++ b/src/providers/wms/qgswmsprovider.cpp @@ -710,9 +710,9 @@ QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, i #if QGISDEBUG int n = ( col1 - col0 + 1 ) * ( row1 - row0 + 1 ); QgsDebugMsg( QString( "tile number: %1x%2 = %3" ).arg( col1 - col0 + 1 ).arg( row1 - row0 + 1 ).arg( n ) ); - if ( n > 100 ) + if ( n > 256 ) { - emit statusChanged( QString( "current view would need %1 tiles. tile request per draw limited to 100." ).arg( n ) ); + emit statusChanged( QString( "current view would need %1 tiles. tile request per draw limited to 256." ).arg( n ) ); return image; } #endif