QGIS API Documentation  3.16.0-Hannover (43b64b13f3)
qgsvectortileloader.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsvectortileloader.cpp
3  --------------------------------------
4  Date : March 2020
5  Copyright : (C) 2020 by Martin Dobias
6  Email : wonder dot sk at gmail dot com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
16 #include "qgsvectortileloader.h"
17 
18 #include <QEventLoop>
19 
21 #include "qgslogger.h"
22 #include "qgsmbtiles.h"
24 #include "qgsvectortileutils.h"
25 #include "qgsapplication.h"
26 #include "qgsauthmanager.h"
27 #include "qgsmessagelog.h"
28 
29 QgsVectorTileLoader::QgsVectorTileLoader( const QString &uri, const QgsTileMatrix &tileMatrix, const QgsTileRange &range, const QPointF &viewCenter, const QString &authid, const QString &referer, QgsFeedback *feedback )
30  : mEventLoop( new QEventLoop )
31  , mFeedback( feedback )
32  , mAuthCfg( authid )
33  , mReferer( referer )
34 {
35  if ( feedback )
36  {
37  connect( feedback, &QgsFeedback::canceled, this, &QgsVectorTileLoader::canceled, Qt::QueuedConnection );
38 
39  // rendering could have been canceled before we started to listen to canceled() signal
40  // so let's check before doing the download and maybe quit prematurely
41  if ( feedback->isCanceled() )
42  return;
43  }
44 
45  QgsDebugMsgLevel( QStringLiteral( "Starting network loader" ), 2 );
46  QVector<QgsTileXYZ> tiles = QgsVectorTileUtils::tilesInRange( range, tileMatrix.zoomLevel() );
48  for ( QgsTileXYZ id : qgis::as_const( tiles ) )
49  {
50  loadFromNetworkAsync( id, tileMatrix, uri );
51  }
52 }
53 
55 {
56  QgsDebugMsgLevel( QStringLiteral( "Terminating network loader" ), 2 );
57 
58  if ( !mReplies.isEmpty() )
59  {
60  // this can happen when the loader is terminated without getting requests finalized
61  // (e.g. downloadBlocking() was not called)
62  canceled();
63  }
64 }
65 
67 {
68  if ( mFeedback && mFeedback->isCanceled() )
69  {
70  QgsDebugMsgLevel( QStringLiteral( "downloadBlocking - not staring event loop - canceled" ), 2 );
71  return; // nothing to do
72  }
73 
74  QgsDebugMsgLevel( QStringLiteral( "Starting event loop with %1 requests" ).arg( mReplies.count() ), 2 );
75 
76  mEventLoop->exec( QEventLoop::ExcludeUserInputEvents );
77 
78  QgsDebugMsgLevel( QStringLiteral( "downloadBlocking finished" ), 2 );
79 
80  Q_ASSERT( mReplies.isEmpty() );
81 }
82 
83 void QgsVectorTileLoader::loadFromNetworkAsync( const QgsTileXYZ &id, const QgsTileMatrix &tileMatrix, const QString &requestUrl )
84 {
85  QString url = QgsVectorTileUtils::formatXYZUrlTemplate( requestUrl, id, tileMatrix );
86  QNetworkRequest request( url );
87  QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsVectorTileLoader" ) );
88  QgsSetRequestInitiatorId( request, id.toString() );
89 
90  request.setAttribute( static_cast<QNetworkRequest::Attribute>( QNetworkRequest::User + 1 ), id.column() );
91  request.setAttribute( static_cast<QNetworkRequest::Attribute>( QNetworkRequest::User + 2 ), id.row() );
92  request.setAttribute( static_cast<QNetworkRequest::Attribute>( QNetworkRequest::User + 3 ), id.zoomLevel() );
93 
94  request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
95  request.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
96 
97  if ( !mReferer.isEmpty() )
98  request.setRawHeader( "Referer", mReferer.toUtf8() );
99 
100  if ( !mAuthCfg.isEmpty() && !QgsApplication::authManager()->updateNetworkRequest( request, mAuthCfg ) )
101  {
102  QgsMessageLog::logMessage( tr( "network request update failed for authentication config" ), tr( "Network" ) );
103  }
104 
105  QNetworkReply *reply = QgsNetworkAccessManager::instance()->get( request );
106  connect( reply, &QNetworkReply::finished, this, &QgsVectorTileLoader::tileReplyFinished );
107 
108  mReplies << reply;
109 }
110 
111 void QgsVectorTileLoader::tileReplyFinished()
112 {
113  QNetworkReply *reply = qobject_cast<QNetworkReply *>( sender() );
114 
115  int reqX = reply->request().attribute( static_cast<QNetworkRequest::Attribute>( QNetworkRequest::User + 1 ) ).toInt();
116  int reqY = reply->request().attribute( static_cast<QNetworkRequest::Attribute>( QNetworkRequest::User + 2 ) ).toInt();
117  int reqZ = reply->request().attribute( static_cast<QNetworkRequest::Attribute>( QNetworkRequest::User + 3 ) ).toInt();
118  QgsTileXYZ tileID( reqX, reqY, reqZ );
119 
120  if ( reply->error() == QNetworkReply::NoError )
121  {
122  // TODO: handle redirections?
123 
124  QgsDebugMsgLevel( QStringLiteral( "Tile download successful: " ) + tileID.toString(), 2 );
125  QByteArray rawData = reply->readAll();
126  mReplies.removeOne( reply );
127  reply->deleteLater();
128 
129  emit tileRequestFinished( QgsVectorTileRawData( tileID, rawData ) );
130  }
131  else
132  {
133  QgsDebugMsg( QStringLiteral( "Tile download failed! " ) + reply->errorString() );
134  mReplies.removeOne( reply );
135  reply->deleteLater();
136 
137  emit tileRequestFinished( QgsVectorTileRawData( tileID, QByteArray() ) );
138  }
139 
140  if ( mReplies.isEmpty() )
141  {
142  // exist the event loop
143  QMetaObject::invokeMethod( mEventLoop.get(), "quit", Qt::QueuedConnection );
144  }
145 }
146 
147 void QgsVectorTileLoader::canceled()
148 {
149  QgsDebugMsgLevel( QStringLiteral( "Canceling %1 pending requests" ).arg( mReplies.count() ), 2 );
150  const QList<QNetworkReply *> replies = mReplies;
151  for ( QNetworkReply *reply : replies )
152  {
153  reply->abort();
154  }
155 }
156 
158 
159 QList<QgsVectorTileRawData> QgsVectorTileLoader::blockingFetchTileRawData( const QString &sourceType, const QString &sourcePath, const QgsTileMatrix &tileMatrix, const QPointF &viewCenter, const QgsTileRange &range, const QString &authid, const QString &referer )
160 {
161  QList<QgsVectorTileRawData> rawTiles;
162 
163  QgsMbTiles mbReader( sourcePath );
164  bool isUrl = ( sourceType == QLatin1String( "xyz" ) );
165  if ( !isUrl )
166  {
167  bool res = mbReader.open();
168  Q_UNUSED( res );
169  Q_ASSERT( res );
170  }
171 
172  QVector<QgsTileXYZ> tiles = QgsVectorTileUtils::tilesInRange( range, tileMatrix.zoomLevel() );
174  for ( QgsTileXYZ id : qgis::as_const( tiles ) )
175  {
176  QByteArray rawData = isUrl ? loadFromNetwork( id, tileMatrix, sourcePath, authid, referer ) : loadFromMBTiles( id, mbReader );
177  if ( !rawData.isEmpty() )
178  {
179  rawTiles.append( QgsVectorTileRawData( id, rawData ) );
180  }
181  }
182  return rawTiles;
183 }
184 
185 QByteArray QgsVectorTileLoader::loadFromNetwork( const QgsTileXYZ &id, const QgsTileMatrix &tileMatrix, const QString &requestUrl, const QString &authid, const QString &referer )
186 {
187  QString url = QgsVectorTileUtils::formatXYZUrlTemplate( requestUrl, id, tileMatrix );
188  QNetworkRequest nr;
189  nr.setUrl( QUrl( url ) );
190 
191  if ( !referer.isEmpty() )
192  nr.setRawHeader( "Referer", referer.toUtf8() );
193 
195  req.setAuthCfg( authid );
196  QgsDebugMsgLevel( QStringLiteral( "Blocking request: " ) + url, 2 );
197  QgsBlockingNetworkRequest::ErrorCode errCode = req.get( nr );
198  if ( errCode != QgsBlockingNetworkRequest::NoError )
199  {
200  QgsDebugMsg( QStringLiteral( "Request failed: " ) + url );
201  return QByteArray();
202  }
203  QgsNetworkReplyContent reply = req.reply();
204  QgsDebugMsgLevel( QStringLiteral( "Request successful, content size %1" ).arg( reply.content().size() ), 2 );
205  return reply.content();
206 }
207 
208 
209 QByteArray QgsVectorTileLoader::loadFromMBTiles( const QgsTileXYZ &id, QgsMbTiles &mbTileReader )
210 {
211  // MBTiles uses TMS specs with Y starting at the bottom while XYZ uses Y starting at the top
212  int rowTMS = pow( 2, id.zoomLevel() ) - id.row() - 1;
213  QByteArray gzippedTileData = mbTileReader.tileData( id.zoomLevel(), id.column(), rowTMS );
214  if ( gzippedTileData.isEmpty() )
215  {
216  QgsDebugMsg( QStringLiteral( "Failed to get tile " ) + id.toString() );
217  return QByteArray();
218  }
219 
220  QByteArray data;
221  if ( !QgsMbTiles::decodeGzip( gzippedTileData, data ) )
222  {
223  QgsDebugMsg( QStringLiteral( "Failed to decompress tile " ) + id.toString() );
224  return QByteArray();
225  }
226 
227  QgsDebugMsgLevel( QStringLiteral( "Tile blob size %1 -> uncompressed size %2" ).arg( gzippedTileData.size() ).arg( data.size() ), 2 );
228  return data;
229 }
QgsVectorTileLoader::~QgsVectorTileLoader
~QgsVectorTileLoader()
Definition: qgsvectortileloader.cpp:54
QgsTileXYZ
Stores coordinates of a tile in a tile matrix set.
Definition: qgstiles.h:33
QgsTileRange
Range of tiles in a tile matrix to be rendered.
Definition: qgstiles.h:66
QgsDebugMsgLevel
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
qgsauthmanager.h
qgsblockingnetworkrequest.h
QgsFeedback::canceled
void canceled()
Internal routines can connect to this signal if they use event loop.
QgsMbTiles::decodeGzip
static bool decodeGzip(const QByteArray &bytesIn, QByteArray &bytesOut)
Decodes gzip byte stream, returns true on success. Useful for reading vector tiles.
Definition: qgsmbtiles.cpp:210
QgsBlockingNetworkRequest::reply
QgsNetworkReplyContent reply() const
Returns the content of the network reply, after a get() or post() request has been made.
Definition: qgsblockingnetworkrequest.h:117
QgsMbTiles::open
bool open()
Tries to open the file, returns true on success.
Definition: qgsmbtiles.cpp:32
QgsVectorTileLoader::blockingFetchTileRawData
static QList< QgsVectorTileRawData > blockingFetchTileRawData(const QString &sourceType, const QString &sourcePath, const QgsTileMatrix &tileMatrix, const QPointF &viewCenter, const QgsTileRange &range, const QString &authid, const QString &referer)
Returns raw tile data for the specified range of tiles. Blocks the caller until all tiles are fetched...
Definition: qgsvectortileloader.cpp:159
QgsDebugMsg
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QgsMbTiles::tileData
QByteArray tileData(int z, int x, int y)
Returns raw tile data for given tile.
Definition: qgsmbtiles.cpp:146
QgsSetRequestInitiatorClass
#define QgsSetRequestInitiatorClass(request, _class)
Definition: qgsnetworkaccessmanager.h:41
qgsmbtiles.h
QgsApplication::authManager
static QgsAuthManager * authManager()
Returns the application's authentication manager instance.
Definition: qgsapplication.cpp:1282
QgsVectorTileLoader::downloadBlocking
void downloadBlocking()
Blocks the caller until all asynchronous requests are finished (with a success or a failure)
Definition: qgsvectortileloader.cpp:66
qgsapplication.h
QgsTileMatrix::zoomLevel
int zoomLevel() const
Returns zoom level of the tile matrix.
Definition: qgstiles.h:110
QgsBlockingNetworkRequest::ErrorCode
ErrorCode
Error codes.
Definition: qgsblockingnetworkrequest.h:53
QgsVectorTileUtils::tilesInRange
static QVector< QgsTileXYZ > tilesInRange(const QgsTileRange &range, int zoomLevel)
Returns a list of tiles in the given tile range.
Definition: qgsvectortileutils.cpp:163
QgsTileMatrix
Defines a matrix of tiles for a single zoom level: it is defined by its size (width * height) and map...
Definition: qgstiles.h:103
QgsMbTiles
Utility class for reading and writing MBTiles files (which are SQLite3 databases).
Definition: qgsmbtiles.h:39
QgsVectorTileLoader::tileRequestFinished
void tileRequestFinished(const QgsVectorTileRawData &rawTile)
Emitted when a tile request has finished. If a tile request has failed, the returned raw tile byte ar...
QgsVectorTileLoader::loadFromNetwork
static QByteArray loadFromNetwork(const QgsTileXYZ &id, const QgsTileMatrix &tileMatrix, const QString &requestUrl, const QString &authid, const QString &referer)
Returns raw tile data for a single tile, doing a HTTP request. Block the caller until tile data are d...
Definition: qgsvectortileloader.cpp:185
QgsFeedback
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition: qgsfeedback.h:44
qgsnetworkaccessmanager.h
QgsVectorTileUtils::sortTilesByDistanceFromCenter
static void sortTilesByDistanceFromCenter(QVector< QgsTileXYZ > &tiles, const QPointF &center)
Orders tile requests according to the distance from view center (given in tile matrix coords)
Definition: qgsvectortileutils.cpp:176
QgsBlockingNetworkRequest::setAuthCfg
void setAuthCfg(const QString &authCfg)
Sets the authentication config id which should be used during the request.
Definition: qgsblockingnetworkrequest.cpp:52
QgsBlockingNetworkRequest::NoError
@ NoError
No error was encountered.
Definition: qgsblockingnetworkrequest.h:54
QgsSetRequestInitiatorId
#define QgsSetRequestInitiatorId(request, str)
Definition: qgsnetworkaccessmanager.h:42
QgsVectorTileLoader::loadFromMBTiles
static QByteArray loadFromMBTiles(const QgsTileXYZ &id, QgsMbTiles &mbTileReader)
Returns raw tile data for a single tile loaded from MBTiles file.
Definition: qgsvectortileloader.cpp:209
QgsNetworkAccessManager::instance
static QgsNetworkAccessManager * instance(Qt::ConnectionType connectionType=Qt::BlockingQueuedConnection)
Returns a pointer to the active QgsNetworkAccessManager for the current thread.
Definition: qgsnetworkaccessmanager.cpp:121
qgsvectortileloader.h
QgsVectorTileLoader::QgsVectorTileLoader
QgsVectorTileLoader(const QString &uri, const QgsTileMatrix &tileMatrix, const QgsTileRange &range, const QPointF &viewCenter, const QString &authid, const QString &referer, QgsFeedback *feedback)
Constructs tile loader for doing asynchronous requests and starts network requests.
Definition: qgsvectortileloader.cpp:29
QgsFeedback::isCanceled
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:53
QgsBlockingNetworkRequest::get
ErrorCode get(QNetworkRequest &request, bool forceRefresh=false, QgsFeedback *feedback=nullptr)
Performs a "get" operation on the specified request.
Definition: qgsblockingnetworkrequest.cpp:57
QgsVectorTileUtils::formatXYZUrlTemplate
static QString formatXYZUrlTemplate(const QString &url, QgsTileXYZ tile, const QgsTileMatrix &tileMatrix)
Returns formatted tile URL string replacing {x}, {y}, {z} placeholders (or {-y} instead of {y} for TM...
Definition: qgsvectortileutils.cpp:124
QgsVectorTileRawData
Keeps track of raw tile data that need to be decoded.
Definition: qgsvectortileloader.h:32
QgsAuthManager::updateNetworkRequest
bool updateNetworkRequest(QNetworkRequest &request, const QString &authcfg, const QString &dataprovider=QString())
Provider call to update a QNetworkRequest with an authentication config.
Definition: qgsauthmanager.cpp:1458
QgsMessageLog::logMessage
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
Definition: qgsmessagelog.cpp:27
QgsNetworkReplyContent::content
QByteArray content() const
Returns the reply content.
Definition: qgsnetworkreply.h:158
QgsNetworkReplyContent
Encapsulates a network reply within a container which is inexpensive to copy and safe to pass between...
Definition: qgsnetworkreply.h:29
qgslogger.h
qgsvectortileutils.h
QgsBlockingNetworkRequest
A thread safe class for performing blocking (sync) network requests, with full support for QGIS proxy...
Definition: qgsblockingnetworkrequest.h:47
qgsmessagelog.h