Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
propagate error from XYZ raster tile and vector tile to UI
  • Loading branch information
vcloarec authored and nyalldawson committed Jan 11, 2022
1 parent 24a498b commit ab680c7
Show file tree
Hide file tree
Showing 11 changed files with 105 additions and 18 deletions.
5 changes: 5 additions & 0 deletions src/app/qgisapp.cpp
Expand Up @@ -4973,6 +4973,11 @@ void QgisApp::initLayerTreeView()

connect( mMapCanvas, &QgsMapCanvas::mapCanvasRefreshed, this, &QgisApp::updateFilterLegend );
connect( mMapCanvas, &QgsMapCanvas::renderErrorOccurred, badLayerIndicatorProvider, &QgsLayerTreeViewBadLayerIndicatorProvider::reportLayerError );
connect( mMapCanvas, &QgsMapCanvas::renderErrorOccurred, mInfoBar, [this]( const QString & error, QgsMapLayer * layer )
{
QString message = layer->name() + QStringLiteral( ": " ) + error;
mInfoBar->pushItem( new QgsMessageBarItem( message, Qgis::MessageLevel::Warning, 60 ) );
} );
}

void QgisApp::setupLayerTreeViewFromSettings()
Expand Down
2 changes: 1 addition & 1 deletion src/app/qgslayertreeviewbadlayerindicator.cpp
Expand Up @@ -82,7 +82,7 @@ void QgsLayerTreeViewBadLayerIndicatorProvider::onIndicatorClicked( const QModel
QgsMessageViewer *m = new QgsMessageViewer( QgisApp::instance() );
m->setWindowTitle( tr( "Layer Error" ) );
if ( thisLayerErrors.count() == 1 )
m->setMessageAsPlainText( thisLayerErrors.at( 0 ) );
m->setMessageAsHtml( thisLayerErrors.at( 0 ) );
else
{
QString message = QStringLiteral( "<ul>" );
Expand Down
4 changes: 1 addition & 3 deletions src/core/qgstiledownloadmanager.cpp
Expand Up @@ -109,13 +109,11 @@ void QgsTileDownloadManagerReplyWorkerObject::replyFinished()
QgsDebugMsgLevel( QStringLiteral( "Tile download manager: internal reply finished: " ) + mRequest.url().toString(), 2 );

QNetworkReply *reply = qobject_cast<QNetworkReply *>( sender() );
QByteArray data;
QByteArray data = reply->readAll();;

if ( reply->error() == QNetworkReply::NoError )
{
++mManager->mStats.networkRequestsOk;

data = reply->readAll();
}
else
{
Expand Down
2 changes: 2 additions & 0 deletions src/core/vectortile/qgsvectortilelayerrenderer.cpp
Expand Up @@ -183,6 +183,8 @@ bool QgsVectorTileLayerRenderer::render()
// Block until tiles are fetched and rendered. If the rendering gets canceled at some point,
// the async loader will catch the signal, abort requests and return from downloadBlocking()
asyncLoader->downloadBlocking();
if ( !asyncLoader->error().isEmpty() )
mErrors.append( asyncLoader->error() );
}

mRenderer->stopRender( ctx );
Expand Down
12 changes: 12 additions & 0 deletions src/core/vectortile/qgsvectortileloader.cpp
Expand Up @@ -130,6 +130,13 @@ void QgsVectorTileLoader::tileReplyFinished()
}
else
{
if ( reply->error() == QNetworkReply::ContentAccessDenied )
{
mError = tr( "Access denied" );
if ( !reply->data().isEmpty() )
mError.append( QStringLiteral( ": " ) + reply->data() );
}

QgsDebugMsg( QStringLiteral( "Tile download failed! " ) + reply->errorString() );
mReplies.removeOne( reply );
reply->deleteLater();
Expand All @@ -155,6 +162,11 @@ void QgsVectorTileLoader::canceled()

}

QString QgsVectorTileLoader::error() const
{
return mError;
}

//////

QList<QgsVectorTileRawData> QgsVectorTileLoader::blockingFetchTileRawData( const QString &sourceType, const QString &sourcePath, const QgsTileMatrix &tileMatrix, const QPointF &viewCenter, const QgsTileRange &range, const QString &authid, const QgsHttpHeaders &headers )
Expand Down
5 changes: 5 additions & 0 deletions src/core/vectortile/qgsvectortileloader.h
Expand Up @@ -91,6 +91,9 @@ class QgsVectorTileLoader : public QObject
//! Blocks the caller until all asynchronous requests are finished (with a success or a failure)
void downloadBlocking();

//! Returns a eventual error that occured during loading, void if no error.
QString error() const;

private:
void loadFromNetworkAsync( const QgsTileXYZ &id, const QgsTileMatrix &tileMatrix, const QString &requestUrl );

Expand All @@ -114,6 +117,8 @@ class QgsVectorTileLoader : public QObject
//! Running tile requests
QList<QgsTileDownloadManagerReply *> mReplies;

QString mError;

};

#endif // QGSVECTORTILELOADER_H
2 changes: 2 additions & 0 deletions src/gui/layertree/qgslayertreeview.cpp
Expand Up @@ -467,8 +467,10 @@ void QgsLayerTreeView::addIndicator( QgsLayerTreeNode *node, QgsLayerTreeViewInd
connect( indicator, &QgsLayerTreeViewIndicator::changed, this, [ = ]
{
update();
viewport()->repaint();
} );
update();
viewport()->repaint(); //update() does not automatically trigger a repaint()
}
}

Expand Down
39 changes: 31 additions & 8 deletions src/gui/qgsmapcanvas.cpp
Expand Up @@ -725,14 +725,7 @@ void QgsMapCanvas::rendererJobFinished()

mMapUpdateTimer.stop();

// TODO: would be better to show the errors in message bar
const auto constErrors = mJob->errors();
for ( const QgsMapRendererJob::Error &error : constErrors )
{
QgsMapLayer *layer = QgsProject::instance()->mapLayer( error.layerID );
emit renderErrorOccurred( error.message, layer );
QgsMessageLog::logMessage( error.layerID + " :: " + error.message, tr( "Rendering" ) );
}
notifyRendererErrors( mJob->errors() );

if ( !mJobCanceled )
{
Expand Down Expand Up @@ -1038,6 +1031,36 @@ void QgsMapCanvas::showContextMenu( QgsMapMouseEvent *event )
menu.exec( event->globalPos() );
}

void QgsMapCanvas::notifyRendererErrors( const QgsMapRendererJob::Errors &errors )
{
const QDateTime currentTime = QDateTime::currentDateTime();

// remove errors too old
for ( const QgsMapRendererJob::Error &error : errors )
{
const QString errorKey = error.layerID + ':' + error.message;
if ( mRendererErrors.contains( errorKey ) )
{
const QDateTime sameErrorTime = mRendererErrors.value( errorKey );

if ( sameErrorTime.secsTo( currentTime ) < 60 )
continue;
}

mRendererErrors[errorKey] = currentTime;

QString message = error.message;
const QRegularExpression regEx( QStringLiteral( "(https?:\\/\\/+[\\/\\{\\}\\?=a-zA-Z0-9_.~-]*)" ), QRegularExpression::CaseInsensitiveOption );
QRegularExpressionMatch match = regEx.match( message );
if ( match.hasMatch() )
message.replace( regEx, "<a href=\"\\1\">\\1</a>" );

QgsMapLayer *layer = QgsProject::instance()->mapLayer( error.layerID );
emit renderErrorOccurred( message, layer );
QgsMessageLog::logMessage( error.layerID + " :: " + message, tr( "Rendering" ) );
}
}

void QgsMapCanvas::updateDevicePixelFromScreen()
{
mSettings.setDevicePixelRatio( devicePixelRatio() );
Expand Down
16 changes: 16 additions & 0 deletions src/gui/qgsmapcanvas.h
Expand Up @@ -30,6 +30,7 @@
#include "qgsmapcanvasinteractionblocker.h"
#include "qgsproject.h"
#include "qgsdistancearea.h"
#include "qgsmaprendererjob.h"

#include <QDomDocument>
#include <QGraphicsView>
Expand Down Expand Up @@ -1422,6 +1423,15 @@ class GUI_EXPORT QgsMapCanvas : public QGraphicsView, public QgsExpressionContex

std::unique_ptr< QgsTemporaryCursorOverride > mTemporaryCursorOverride;

/**
* This attribute maps error strings occured during rendering with time.
* The string contains the layerId with the error message ("layerId:error").
* This is used to avoid propagatation of repeated error message from renderer
* in a short time range (\see notifyRendererErrors())
*
*/
QMap <QString, QDateTime> mRendererErrors;

/**
* Returns the last cursor position on the canvas in geographical coordinates
* \since QGIS 3.4
Expand Down Expand Up @@ -1492,6 +1502,12 @@ class GUI_EXPORT QgsMapCanvas : public QGraphicsView, public QgsExpressionContex

void showContextMenu( QgsMapMouseEvent *event );

/**
* This private method is used to emit rendering error from map layer without throwing it for every render.
* It contains a mechanism that does not emit the error if the same error from the same layer was emitted less than 1 mn ago.
*/
void notifyRendererErrors( const QgsMapRendererJob::Errors &errors );

friend class TestQgsMapCanvas;

}; // class QgsMapCanvas
Expand Down
30 changes: 25 additions & 5 deletions src/providers/wms/qgswmsprovider.cpp
Expand Up @@ -978,8 +978,20 @@ QImage *QgsWmsProvider::draw( QgsRectangle const &viewExtent, int pixelWidth, in
cmp.center = viewExtent.center();
std::sort( requestsFinal.begin(), requestsFinal.end(), cmp );

QgsWmsTiledImageDownloadHandler handler( dataSourceUri(), mSettings.authorization(), mTileReqNo, requestsFinal, image, viewExtent, mSettings.mSmoothPixmapTransform, feedback );
QgsWmsTiledImageDownloadHandler handler(
dataSourceUri(),
mSettings.authorization(),
mTileReqNo,
requestsFinal,
image,
viewExtent,
mSettings.mSmoothPixmapTransform,
feedback );

handler.downloadBlocking();

if ( feedback )
feedback->appendError( handler.error() );
}

QgsDebugMsgLevel( QStringLiteral( "TILE CACHE total: %1 / %2" ).arg( QgsTileCache::totalCost() ).arg( QgsTileCache::maxCost() ), 3 );
Expand Down Expand Up @@ -4395,7 +4407,6 @@ void QgsWmsTiledImageDownloadHandler::tileReplyFinished()
if ( !status.isNull() && status.toInt() >= 400 )
{
QVariant phrase = reply->attribute( QNetworkRequest::HttpReasonPhraseAttribute );

QgsWmsProvider::showMessageBox( tr( "Tile request error" ), tr( "Status: %1\nReason phrase: %2" ).arg( status.toInt() ).arg( phrase.toString() ) );

mReplies.removeOne( reply );
Expand Down Expand Up @@ -4511,10 +4522,14 @@ void QgsWmsTiledImageDownloadHandler::tileReplyFinished()
{
QgsWmsStatistics::Stat &stat = QgsWmsStatistics::statForUri( mProviderUri );
stat.errors++;

// if we reached timeout, let's try again (e.g. in case of slow connection or slow server)
if ( reply->error() == QNetworkReply::TimeoutError )
repeatTileRequest( reply->request() );
repeatTileRequest( reply->request() );

if ( reply->error() == QNetworkReply::ContentAccessDenied )
{
mError = tr( "Access denied: %1" ).
arg( reply->attribute( QNetworkRequest::HttpReasonPhraseAttribute ).toString() );
}
}
}

Expand Down Expand Up @@ -4591,6 +4606,11 @@ void QgsWmsTiledImageDownloadHandler::repeatTileRequest( QNetworkRequest const &
connect( reply, &QNetworkReply::finished, this, &QgsWmsTiledImageDownloadHandler::tileReplyFinished );
}

QString QgsWmsTiledImageDownloadHandler::error() const
{
return mError;
}

// Some servers like http://glogow.geoportal2.pl/map/wms/wms.php? do not BBOX
// to be formatted with excessive precision. As a double is exactly represented
// with 19 decimal figures, do not attempt to output more
Expand Down
6 changes: 5 additions & 1 deletion src/providers/wms/qgswmsprovider.h
Expand Up @@ -634,6 +634,8 @@ class QgsWmsTiledImageDownloadHandler : public QObject

void downloadBlocking();

QString error() const;

protected slots:
void tileReplyFinished();
void canceled();
Expand All @@ -652,7 +654,7 @@ class QgsWmsTiledImageDownloadHandler : public QObject
void finish() { QMetaObject::invokeMethod( mEventLoop, "quit", Qt::QueuedConnection ); }

QString mProviderUri;

QString mBaseUrl;
QgsWmsAuthorization mAuth;

QImage *mImage = nullptr;
Expand All @@ -667,6 +669,8 @@ class QgsWmsTiledImageDownloadHandler : public QObject
QList<QNetworkReply *> mReplies;

QgsRasterBlockFeedback *mFeedback = nullptr;

QString mError;
};


Expand Down

0 comments on commit ab680c7

Please sign in to comment.