12#include <QtCore/QFile>
13#include <QtNetwork/QNetworkAccessManager>
14#include <QtNetwork/QNetworkReply>
34bool isBingEmptyTile(
int mapId,
const QByteArray& data)
36 static const QByteArray bingNoTileImage = [] {
37 QFile file(QStringLiteral(
":/res/BingNoTileBytes.dat"));
38 return file.open(QFile::ReadOnly) ? file.readAll() : QByteArray();
41 if (bingNoTileImage.isEmpty() || (data != bingNoTileImage)) {
45 return provider && provider->isBingProvider();
53 _mapId(
UrlFactory::getQtMapIdFromProviderType(mapType)),
54 _networkManager(networkManager ? networkManager : new QNetworkAccessManager(this))
57 qCWarning(GeoMapTileImageSourceLog) <<
"unknown map type" << _mapType <<
"- all tile requests will fail";
63 const int requestId = ++_requestIdCounter;
64 _pending.insert(requestId);
67 _failAsync(requestId);
70 qCDebug(GeoMapTileImageSourceVerboseLog) <<
"requestTileImage: key" << key <<
"requestId" << requestId;
74 const std::unique_ptr<QGCCacheTile> guard(tile);
75 if (!_pending.contains(requestId)) {
78 if (!tile || tile->
img.isEmpty()) {
79 qCDebug(GeoMapTileImageSourceLog) <<
"tile" << key <<
"cache returned empty tile, fetching from network";
80 _fetchFromNetwork(requestId, key);
83 _deliver(requestId, key, tile->
img);
86 if (!_pending.contains(requestId)) {
89 qCDebug(GeoMapTileImageSourceVerboseLog) <<
"tile" << key <<
"not cached, fetching from network";
90 _fetchFromNetwork(requestId, key);
94 qCDebug(GeoMapTileImageSourceLog) <<
"could not queue cache lookup for" << key;
96 _failAsync(requestId);
101void TileImageSource::_failAsync(
int requestId)
104 QMetaObject::invokeMethod(
107 if (_pending.contains(requestId)) {
108 _finishFailed(requestId);
111 Qt::QueuedConnection);
116 if (!_pending.remove(requestId)) {
119 QNetworkReply*
const reply = _activeReplies.take(requestId);
125void TileImageSource::_fetchFromNetwork(
int requestId,
const TileMath::TileKey& key)
128 QNetworkReply*
const reply = _networkManager->get(request);
129 _activeReplies.insert(requestId, reply);
131 connect(reply, &QNetworkReply::finished,
this, [
this, requestId, key, reply] {
132 reply->deleteLater();
133 _activeReplies.remove(requestId);
134 if (!_pending.contains(requestId)) {
137 if (reply->error() != QNetworkReply::NoError) {
138 _warnFailure(key, QStringLiteral(
"network error: %1 (http %2)")
139 .arg(reply->errorString())
140 .arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt()));
141 _finishFailed(requestId);
144 const QByteArray data = reply->readAll();
145 if (data.isEmpty() || isBingEmptyTile(_mapId, data)) {
146 _warnFailure(key, data.isEmpty() ? QStringLiteral(
"network fetch returned empty body")
147 : QStringLiteral(
"network fetch returned Bing no-tile placeholder"));
148 _finishFailed(requestId);
152 if (!image.loadFromData(data)) {
153 _warnFailure(key, QStringLiteral(
"network body failed to decode, %1 bytes").arg(data.size()));
154 _finishFailed(requestId);
158 qCDebug(GeoMapTileImageSourceVerboseLog) <<
"tile" << key <<
"fetched from network, caching";
161 _finishSucceeded(requestId, image);
165void TileImageSource::_deliver(
int requestId,
const TileMath::TileKey& key,
const QByteArray& data)
168 if (isBingEmptyTile(_mapId, data) || !image.loadFromData(data)) {
172 qCDebug(GeoMapTileImageSourceLog) <<
"tile" << key <<
"cached entry unusable, fetching from network";
173 _fetchFromNetwork(requestId, key);
176 qCDebug(GeoMapTileImageSourceVerboseLog) <<
"tile" << key <<
"served from cache";
177 _finishSucceeded(requestId, image);
180void TileImageSource::_finishSucceeded(
int requestId,
const QImage& image)
182 _pending.remove(requestId);
186void TileImageSource::_finishFailed(
int requestId)
188 _pending.remove(requestId);
192void TileImageSource::_warnFailure(
const TileMath::TileKey& key,
const QString& reason)
197 if (!_failureWarnTimer.isValid() || (_failureWarnTimer.elapsed() >= kFailureWarnIntervalMs)) {
198 _failureWarnTimer.restart();
199 qCWarning(GeoMapTileImageSourceLog) <<
"tile" << key <<
"failed:" << reason;
201 qCDebug(GeoMapTileImageSourceLog) <<
"tile" << key <<
"failed:" << reason <<
"(warning suppressed)";
#define QGC_LOGGING_CATEGORY(name, categoryStr)
QGCMapEngine * getQGCMapEngine()
std::shared_ptr< const MapProvider > SharedMapProvider
void tileFetched(QGCCacheTile *tile)
bool addTask(QGCMapTask *task)
void error(QGCMapTask::TaskType type, const QString &errorString)
static QGCFetchTileTask * createFetchTileTask(const QString &type, int x, int y, int z)
static void cacheTile(const QString &type, int x, int y, int z, const QByteArray &image, const QString &format, qulonglong set=UINT64_MAX)
static QNetworkRequest getNetworkRequest(int mapId, int x, int y, int zoom)
void tileImageFailed(int requestId)
void cancelRequest(int requestId)
Cancel a pending request, aborting any in-flight network fetch.
int requestTileImage(const TileMath::TileKey &key)
Request the image for a tile. Returns a request id (> 0).
TileImageSource(const QString &mapType, QObject *parent=nullptr, QNetworkAccessManager *networkManager=nullptr)
void tileImageReady(int requestId, const QImage &image)
static QString getImageFormat(QStringView type, QByteArrayView image)
static std::shared_ptr< const MapProvider > getMapProviderFromQtMapId(int qtMapId)