Branch data Line data Source code
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 : :
20 : : #include "qgsblockingnetworkrequest.h"
21 : : #include "qgslogger.h"
22 : : #include "qgsmbtiles.h"
23 : : #include "qgsnetworkaccessmanager.h"
24 : : #include "qgsvectortileutils.h"
25 : : #include "qgsapplication.h"
26 : : #include "qgsauthmanager.h"
27 : : #include "qgsmessagelog.h"
28 : :
29 : : #include "qgstiledownloadmanager.h"
30 : :
31 : 0 : QgsVectorTileLoader::QgsVectorTileLoader( const QString &uri, const QgsTileMatrix &tileMatrix, const QgsTileRange &range, const QPointF &viewCenter, const QString &authid, const QString &referer, QgsFeedback *feedback )
32 : 0 : : mEventLoop( new QEventLoop )
33 : 0 : , mFeedback( feedback )
34 : 0 : , mAuthCfg( authid )
35 : 0 : , mReferer( referer )
36 : 0 : {
37 : 0 : if ( feedback )
38 : : {
39 : 0 : connect( feedback, &QgsFeedback::canceled, this, &QgsVectorTileLoader::canceled, Qt::QueuedConnection );
40 : :
41 : : // rendering could have been canceled before we started to listen to canceled() signal
42 : : // so let's check before doing the download and maybe quit prematurely
43 : 0 : if ( feedback->isCanceled() )
44 : 0 : return;
45 : 0 : }
46 : :
47 : 0 : QgsDebugMsgLevel( QStringLiteral( "Starting network loader" ), 2 );
48 : 0 : QVector<QgsTileXYZ> tiles = QgsVectorTileUtils::tilesInRange( range, tileMatrix.zoomLevel() );
49 : 0 : QgsVectorTileUtils::sortTilesByDistanceFromCenter( tiles, viewCenter );
50 : 0 : for ( QgsTileXYZ id : std::as_const( tiles ) )
51 : : {
52 : 0 : loadFromNetworkAsync( id, tileMatrix, uri );
53 : : }
54 : 0 : }
55 : :
56 : 0 : QgsVectorTileLoader::~QgsVectorTileLoader()
57 : 0 : {
58 : 0 : QgsDebugMsgLevel( QStringLiteral( "Terminating network loader" ), 2 );
59 : :
60 : 0 : if ( !mReplies.isEmpty() )
61 : : {
62 : : // this can happen when the loader is terminated without getting requests finalized
63 : : // (e.g. downloadBlocking() was not called)
64 : 0 : canceled();
65 : 0 : }
66 : 0 : }
67 : :
68 : 0 : void QgsVectorTileLoader::downloadBlocking()
69 : : {
70 : 0 : if ( mFeedback && mFeedback->isCanceled() )
71 : : {
72 : 0 : QgsDebugMsgLevel( QStringLiteral( "downloadBlocking - not staring event loop - canceled" ), 2 );
73 : 0 : return; // nothing to do
74 : : }
75 : :
76 : 0 : QgsDebugMsgLevel( QStringLiteral( "Starting event loop with %1 requests" ).arg( mReplies.count() ), 2 );
77 : :
78 : 0 : mEventLoop->exec( QEventLoop::ExcludeUserInputEvents );
79 : :
80 : 0 : QgsDebugMsgLevel( QStringLiteral( "downloadBlocking finished" ), 2 );
81 : :
82 : : Q_ASSERT( mReplies.isEmpty() );
83 : 0 : }
84 : :
85 : 0 : void QgsVectorTileLoader::loadFromNetworkAsync( const QgsTileXYZ &id, const QgsTileMatrix &tileMatrix, const QString &requestUrl )
86 : : {
87 : 0 : QString url = QgsVectorTileUtils::formatXYZUrlTemplate( requestUrl, id, tileMatrix );
88 : 0 : QNetworkRequest request( url );
89 : 0 : QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsVectorTileLoader" ) );
90 : 0 : QgsSetRequestInitiatorId( request, id.toString() );
91 : :
92 : 0 : request.setAttribute( static_cast<QNetworkRequest::Attribute>( QNetworkRequest::User + 1 ), id.column() );
93 : 0 : request.setAttribute( static_cast<QNetworkRequest::Attribute>( QNetworkRequest::User + 2 ), id.row() );
94 : 0 : request.setAttribute( static_cast<QNetworkRequest::Attribute>( QNetworkRequest::User + 3 ), id.zoomLevel() );
95 : :
96 : 0 : request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
97 : 0 : request.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
98 : :
99 : 0 : if ( !mReferer.isEmpty() )
100 : 0 : request.setRawHeader( "Referer", mReferer.toUtf8() );
101 : :
102 : 0 : if ( !mAuthCfg.isEmpty() && !QgsApplication::authManager()->updateNetworkRequest( request, mAuthCfg ) )
103 : : {
104 : 0 : QgsMessageLog::logMessage( tr( "network request update failed for authentication config" ), tr( "Network" ) );
105 : 0 : }
106 : :
107 : 0 : QgsTileDownloadManagerReply *reply = QgsApplication::tileDownloadManager()->get( request );
108 : 0 : connect( reply, &QgsTileDownloadManagerReply::finished, this, &QgsVectorTileLoader::tileReplyFinished );
109 : 0 : mReplies << reply;
110 : 0 : }
111 : :
112 : 0 : void QgsVectorTileLoader::tileReplyFinished()
113 : : {
114 : 0 : QgsTileDownloadManagerReply *reply = qobject_cast<QgsTileDownloadManagerReply *>( sender() );
115 : :
116 : 0 : int reqX = reply->request().attribute( static_cast<QNetworkRequest::Attribute>( QNetworkRequest::User + 1 ) ).toInt();
117 : 0 : int reqY = reply->request().attribute( static_cast<QNetworkRequest::Attribute>( QNetworkRequest::User + 2 ) ).toInt();
118 : 0 : int reqZ = reply->request().attribute( static_cast<QNetworkRequest::Attribute>( QNetworkRequest::User + 3 ) ).toInt();
119 : 0 : QgsTileXYZ tileID( reqX, reqY, reqZ );
120 : :
121 : 0 : if ( reply->error() == QNetworkReply::NoError )
122 : : {
123 : : // TODO: handle redirections?
124 : :
125 : 0 : QgsDebugMsgLevel( QStringLiteral( "Tile download successful: " ) + tileID.toString(), 2 );
126 : 0 : QByteArray rawData = reply->data();
127 : 0 : mReplies.removeOne( reply );
128 : 0 : reply->deleteLater();
129 : :
130 : 0 : emit tileRequestFinished( QgsVectorTileRawData( tileID, rawData ) );
131 : 0 : }
132 : : else
133 : : {
134 : 0 : QgsDebugMsg( QStringLiteral( "Tile download failed! " ) + reply->errorString() );
135 : 0 : mReplies.removeOne( reply );
136 : 0 : reply->deleteLater();
137 : :
138 : 0 : emit tileRequestFinished( QgsVectorTileRawData( tileID, QByteArray() ) );
139 : : }
140 : :
141 : 0 : if ( mReplies.isEmpty() )
142 : : {
143 : : // exist the event loop
144 : 0 : QMetaObject::invokeMethod( mEventLoop.get(), "quit", Qt::QueuedConnection );
145 : 0 : }
146 : 0 : }
147 : :
148 : 0 : void QgsVectorTileLoader::canceled()
149 : : {
150 : 0 : QgsDebugMsgLevel( QStringLiteral( "Canceling %1 pending requests" ).arg( mReplies.count() ), 2 );
151 : 0 : qDeleteAll( mReplies );
152 : 0 : mReplies.clear();
153 : :
154 : : // stop blocking download
155 : 0 : mEventLoop->quit();
156 : :
157 : 0 : }
158 : :
159 : : //////
160 : :
161 : 0 : 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 )
162 : : {
163 : 0 : QList<QgsVectorTileRawData> rawTiles;
164 : :
165 : 0 : QgsMbTiles mbReader( sourcePath );
166 : 0 : bool isUrl = ( sourceType == QLatin1String( "xyz" ) );
167 : 0 : if ( !isUrl )
168 : : {
169 : 0 : bool res = mbReader.open();
170 : : Q_UNUSED( res );
171 : : Q_ASSERT( res );
172 : 0 : }
173 : :
174 : 0 : QVector<QgsTileXYZ> tiles = QgsVectorTileUtils::tilesInRange( range, tileMatrix.zoomLevel() );
175 : 0 : QgsVectorTileUtils::sortTilesByDistanceFromCenter( tiles, viewCenter );
176 : 0 : for ( QgsTileXYZ id : std::as_const( tiles ) )
177 : : {
178 : 0 : QByteArray rawData = isUrl ? loadFromNetwork( id, tileMatrix, sourcePath, authid, referer ) : loadFromMBTiles( id, mbReader );
179 : 0 : if ( !rawData.isEmpty() )
180 : : {
181 : 0 : rawTiles.append( QgsVectorTileRawData( id, rawData ) );
182 : 0 : }
183 : 0 : }
184 : 0 : return rawTiles;
185 : 0 : }
186 : :
187 : 0 : QByteArray QgsVectorTileLoader::loadFromNetwork( const QgsTileXYZ &id, const QgsTileMatrix &tileMatrix, const QString &requestUrl, const QString &authid, const QString &referer )
188 : : {
189 : 0 : QString url = QgsVectorTileUtils::formatXYZUrlTemplate( requestUrl, id, tileMatrix );
190 : 0 : QNetworkRequest nr;
191 : 0 : nr.setUrl( QUrl( url ) );
192 : :
193 : 0 : if ( !referer.isEmpty() )
194 : 0 : nr.setRawHeader( "Referer", referer.toUtf8() );
195 : :
196 : 0 : QgsBlockingNetworkRequest req;
197 : 0 : req.setAuthCfg( authid );
198 : 0 : QgsDebugMsgLevel( QStringLiteral( "Blocking request: " ) + url, 2 );
199 : 0 : QgsBlockingNetworkRequest::ErrorCode errCode = req.get( nr );
200 : 0 : if ( errCode != QgsBlockingNetworkRequest::NoError )
201 : : {
202 : 0 : QgsDebugMsg( QStringLiteral( "Request failed: " ) + url );
203 : 0 : return QByteArray();
204 : : }
205 : 0 : QgsNetworkReplyContent reply = req.reply();
206 : 0 : QgsDebugMsgLevel( QStringLiteral( "Request successful, content size %1" ).arg( reply.content().size() ), 2 );
207 : 0 : return reply.content();
208 : 0 : }
209 : :
210 : :
211 : 0 : QByteArray QgsVectorTileLoader::loadFromMBTiles( const QgsTileXYZ &id, QgsMbTiles &mbTileReader )
212 : : {
213 : : // MBTiles uses TMS specs with Y starting at the bottom while XYZ uses Y starting at the top
214 : 0 : int rowTMS = pow( 2, id.zoomLevel() ) - id.row() - 1;
215 : 0 : QByteArray gzippedTileData = mbTileReader.tileData( id.zoomLevel(), id.column(), rowTMS );
216 : 0 : if ( gzippedTileData.isEmpty() )
217 : : {
218 : 0 : QgsDebugMsg( QStringLiteral( "Failed to get tile " ) + id.toString() );
219 : 0 : return QByteArray();
220 : : }
221 : :
222 : 0 : QByteArray data;
223 : 0 : if ( !QgsMbTiles::decodeGzip( gzippedTileData, data ) )
224 : : {
225 : 0 : QgsDebugMsg( QStringLiteral( "Failed to decompress tile " ) + id.toString() );
226 : 0 : return QByteArray();
227 : : }
228 : :
229 : 0 : QgsDebugMsgLevel( QStringLiteral( "Tile blob size %1 -> uncompressed size %2" ).arg( gzippedTileData.size() ).arg( data.size() ), 2 );
230 : 0 : return data;
231 : 0 : }
|