QGIS API Documentation  3.14.0-Pi (9f7028fd23)
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 
26 QgsVectorTileLoader::QgsVectorTileLoader( const QString &uri, const QgsTileMatrix &tileMatrix, const QgsTileRange &range, const QPointF &viewCenter, QgsFeedback *feedback )
27  : mEventLoop( new QEventLoop )
28  , mFeedback( feedback )
29 {
30  if ( feedback )
31  {
32  connect( feedback, &QgsFeedback::canceled, this, &QgsVectorTileLoader::canceled, Qt::QueuedConnection );
33 
34  // rendering could have been canceled before we started to listen to canceled() signal
35  // so let's check before doing the download and maybe quit prematurely
36  if ( feedback->isCanceled() )
37  return;
38  }
39 
40  QgsDebugMsgLevel( QStringLiteral( "Starting network loader" ), 2 );
41  QVector<QgsTileXYZ> tiles = QgsVectorTileUtils::tilesInRange( range, tileMatrix.zoomLevel() );
43  for ( QgsTileXYZ id : qgis::as_const( tiles ) )
44  {
45  loadFromNetworkAsync( id, tileMatrix, uri );
46  }
47 }
48 
50 {
51  QgsDebugMsgLevel( QStringLiteral( "Terminating network loader" ), 2 );
52 
53  if ( !mReplies.isEmpty() )
54  {
55  // this can happen when the loader is terminated without getting requests finalized
56  // (e.g. downloadBlocking() was not called)
57  canceled();
58  }
59 }
60 
62 {
63  if ( mFeedback && mFeedback->isCanceled() )
64  {
65  QgsDebugMsgLevel( QStringLiteral( "downloadBlocking - not staring event loop - canceled" ), 2 );
66  return; // nothing to do
67  }
68 
69  QgsDebugMsgLevel( QStringLiteral( "Starting event loop with %1 requests" ).arg( mReplies.count() ), 2 );
70 
71  mEventLoop->exec( QEventLoop::ExcludeUserInputEvents );
72 
73  QgsDebugMsgLevel( QStringLiteral( "downloadBlocking finished" ), 2 );
74 
75  Q_ASSERT( mReplies.isEmpty() );
76 }
77 
78 void QgsVectorTileLoader::loadFromNetworkAsync( const QgsTileXYZ &id, const QgsTileMatrix &tileMatrix, const QString &requestUrl )
79 {
80  QString url = QgsVectorTileUtils::formatXYZUrlTemplate( requestUrl, id, tileMatrix );
81  QNetworkRequest request( url );
82  QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsVectorTileLoader" ) );
83  QgsSetRequestInitiatorId( request, id.toString() );
84 
85  request.setAttribute( static_cast<QNetworkRequest::Attribute>( QNetworkRequest::User + 1 ), id.column() );
86  request.setAttribute( static_cast<QNetworkRequest::Attribute>( QNetworkRequest::User + 2 ), id.row() );
87  request.setAttribute( static_cast<QNetworkRequest::Attribute>( QNetworkRequest::User + 3 ), id.zoomLevel() );
88 
89  request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
90  request.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
91 
92  QNetworkReply *reply = QgsNetworkAccessManager::instance()->get( request );
93  connect( reply, &QNetworkReply::finished, this, &QgsVectorTileLoader::tileReplyFinished );
94 
95  mReplies << reply;
96 }
97 
98 void QgsVectorTileLoader::tileReplyFinished()
99 {
100  QNetworkReply *reply = qobject_cast<QNetworkReply *>( sender() );
101 
102  int reqX = reply->request().attribute( static_cast<QNetworkRequest::Attribute>( QNetworkRequest::User + 1 ) ).toInt();
103  int reqY = reply->request().attribute( static_cast<QNetworkRequest::Attribute>( QNetworkRequest::User + 2 ) ).toInt();
104  int reqZ = reply->request().attribute( static_cast<QNetworkRequest::Attribute>( QNetworkRequest::User + 3 ) ).toInt();
105  QgsTileXYZ tileID( reqX, reqY, reqZ );
106 
107  if ( reply->error() == QNetworkReply::NoError )
108  {
109  // TODO: handle redirections?
110 
111  QgsDebugMsgLevel( QStringLiteral( "Tile download successful: " ) + tileID.toString(), 2 );
112  QByteArray rawData = reply->readAll();
113  mReplies.removeOne( reply );
114  reply->deleteLater();
115 
116  emit tileRequestFinished( QgsVectorTileRawData( tileID, rawData ) );
117  }
118  else
119  {
120  QgsDebugMsg( QStringLiteral( "Tile download failed! " ) + reply->errorString() );
121  mReplies.removeOne( reply );
122  reply->deleteLater();
123 
124  emit tileRequestFinished( QgsVectorTileRawData( tileID, QByteArray() ) );
125  }
126 
127  if ( mReplies.isEmpty() )
128  {
129  // exist the event loop
130  QMetaObject::invokeMethod( mEventLoop.get(), "quit", Qt::QueuedConnection );
131  }
132 }
133 
134 void QgsVectorTileLoader::canceled()
135 {
136  QgsDebugMsgLevel( QStringLiteral( "Canceling %1 pending requests" ).arg( mReplies.count() ), 2 );
137  const QList<QNetworkReply *> replies = mReplies;
138  for ( QNetworkReply *reply : replies )
139  {
140  reply->abort();
141  }
142 }
143 
145 
146 QList<QgsVectorTileRawData> QgsVectorTileLoader::blockingFetchTileRawData( const QString &sourceType, const QString &sourcePath, const QgsTileMatrix &tileMatrix, const QPointF &viewCenter, const QgsTileRange &range )
147 {
148  QList<QgsVectorTileRawData> rawTiles;
149 
150  QgsMbTiles mbReader( sourcePath );
151  bool isUrl = ( sourceType == QStringLiteral( "xyz" ) );
152  if ( !isUrl )
153  {
154  bool res = mbReader.open();
155  Q_ASSERT( res );
156  }
157 
158  QVector<QgsTileXYZ> tiles = QgsVectorTileUtils::tilesInRange( range, tileMatrix.zoomLevel() );
160  for ( QgsTileXYZ id : qgis::as_const( tiles ) )
161  {
162  QByteArray rawData = isUrl ? loadFromNetwork( id, tileMatrix, sourcePath ) : loadFromMBTiles( id, mbReader );
163  if ( !rawData.isEmpty() )
164  {
165  rawTiles.append( QgsVectorTileRawData( id, rawData ) );
166  }
167  }
168  return rawTiles;
169 }
170 
171 QByteArray QgsVectorTileLoader::loadFromNetwork( const QgsTileXYZ &id, const QgsTileMatrix &tileMatrix, const QString &requestUrl )
172 {
173  QString url = QgsVectorTileUtils::formatXYZUrlTemplate( requestUrl, id, tileMatrix );
174  QNetworkRequest nr;
175  nr.setUrl( QUrl( url ) );
177  QgsDebugMsgLevel( QStringLiteral( "Blocking request: " ) + url, 2 );
178  QgsBlockingNetworkRequest::ErrorCode errCode = req.get( nr );
179  if ( errCode != QgsBlockingNetworkRequest::NoError )
180  {
181  QgsDebugMsg( QStringLiteral( "Request failed: " ) + url );
182  return QByteArray();
183  }
184  QgsNetworkReplyContent reply = req.reply();
185  QgsDebugMsgLevel( QStringLiteral( "Request successful, content size %1" ).arg( reply.content().size() ), 2 );
186  return reply.content();
187 }
188 
189 
190 QByteArray QgsVectorTileLoader::loadFromMBTiles( const QgsTileXYZ &id, QgsMbTiles &mbTileReader )
191 {
192  // MBTiles uses TMS specs with Y starting at the bottom while XYZ uses Y starting at the top
193  int rowTMS = pow( 2, id.zoomLevel() ) - id.row() - 1;
194  QByteArray gzippedTileData = mbTileReader.tileData( id.zoomLevel(), id.column(), rowTMS );
195  if ( gzippedTileData.isEmpty() )
196  {
197  QgsDebugMsg( QStringLiteral( "Failed to get tile " ) + id.toString() );
198  return QByteArray();
199  }
200 
201  QByteArray data;
202  if ( !QgsMbTiles::decodeGzip( gzippedTileData, data ) )
203  {
204  QgsDebugMsg( QStringLiteral( "Failed to decompress tile " ) + id.toString() );
205  return QByteArray();
206  }
207 
208  QgsDebugMsgLevel( QStringLiteral( "Tile blob size %1 -> uncompressed size %2" ).arg( gzippedTileData.size() ).arg( data.size() ), 2 );
209  return data;
210 }
QgsVectorTileLoader::~QgsVectorTileLoader
~QgsVectorTileLoader()
Definition: qgsvectortileloader.cpp:49
QgsTileXYZ
Definition: qgstiles.h:32
QgsTileRange
Definition: qgstiles.h:65
QgsDebugMsgLevel
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
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
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
QgsVectorTileLoader::downloadBlocking
void downloadBlocking()
Blocks the caller until all asynchronous requests are finished (with a success or a failure)
Definition: qgsvectortileloader.cpp:61
QgsTileMatrix::zoomLevel
int zoomLevel() const
Returns zoom level of the tile matrix.
Definition: qgstiles.h:110
QgsBlockingNetworkRequest::ErrorCode
ErrorCode
Error codes.
Definition: qgsblockingnetworkrequest.h:52
QgsVectorTileUtils::tilesInRange
static QVector< QgsTileXYZ > tilesInRange(const QgsTileRange &range, int zoomLevel)
Returns a list of tiles in the given tile range.
Definition: qgsvectortileutils.cpp:158
QgsTileMatrix
Definition: qgstiles.h:102
QgsMbTiles
Definition: qgsmbtiles.h:38
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)
Returns raw tile data for a single tile, doing a HTTP request. Block the caller until tile data are d...
Definition: qgsvectortileloader.cpp:171
QgsFeedback
Definition: qgsfeedback.h:43
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:171
QgsVectorTileLoader::blockingFetchTileRawData
static QList< QgsVectorTileRawData > blockingFetchTileRawData(const QString &sourceType, const QString &sourcePath, const QgsTileMatrix &tileMatrix, const QPointF &viewCenter, const QgsTileRange &range)
Returns raw tile data for the specified range of tiles. Blocks the caller until all tiles are fetched...
Definition: qgsvectortileloader.cpp:146
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:190
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
QgsFeedback::isCanceled
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:66
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:119
QgsVectorTileRawData
Definition: qgsvectortileloader.h:31
QgsNetworkReplyContent::content
QByteArray content() const
Returns the reply content.
Definition: qgsnetworkreply.h:171
QgsNetworkReplyContent
Encapsulates a network reply within a container which is inexpensive to copy and safe to pass between...
Definition: qgsnetworkreply.h:28
qgslogger.h
qgsvectortileutils.h
QgsBlockingNetworkRequest
A thread safe class for performing blocking (sync) network requests, with full support for QGIS proxy...
Definition: qgsblockingnetworkrequest.h:46
QgsVectorTileLoader::QgsVectorTileLoader
QgsVectorTileLoader(const QString &uri, const QgsTileMatrix &tileMatrix, const QgsTileRange &range, const QPointF &viewCenter, QgsFeedback *feedback)
Constructs tile loader for doing asynchronous requests and starts network requests.
Definition: qgsvectortileloader.cpp:26