diff --git a/src/map/loader/tilecache.cpp b/src/map/loader/tilecache.cpp index 60059a4..462dd1c 100644 --- a/src/map/loader/tilecache.cpp +++ b/src/map/loader/tilecache.cpp @@ -1,100 +1,142 @@ /* Copyright (C) 2020 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "tilecache.h" #include #include #include #include #include #include #include #include #include using namespace KOSMIndoorMap; Tile Tile::fromCoordinate(double lat, double lon, uint8_t z) { Tile t; t.x = std::floor((lon + 180.0) / 360.0 * (1 << z)); const auto latrad = OSM::degToRad(lat); t.y = std::floor((1.0 - std::asinh(std::tan(latrad)) / M_PI) / 2.0 * (1 << z)); t.z = z; return t; } TileCache::TileCache(QObject *parent) : QObject(parent) , m_nam(new QNetworkAccessManager(this)) { m_nam->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy); m_nam->enableStrictTransportSecurityStore(true, QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1String("/org.kde.osm/hsts/")); m_nam->setStrictTransportSecurityEnabled(true); } TileCache::~TileCache() = default; QString TileCache::cachedTile(Tile tile) const { const auto p = cachePath(tile); if (QFile::exists(p)) { return p; } return {}; } +void TileCache::ensureCached(Tile tile) +{ + if (!cachedTile(tile).isEmpty()) { + return; + } + downloadTile(tile); +} + void TileCache::downloadTile(Tile tile) { - // TODO queue multiple requests and process at most N in parallel + m_pendingDownloads.push_back(tile); + downloadNext(); +} + +QString TileCache::cachePath(Tile tile) const +{ + return QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + + QLatin1String("/org.kde.osm/vectorosm/") + + QString::number(tile.z) + QLatin1Char('/') + + QString::number(tile.x) + QLatin1Char('/') + + QString::number(tile.y) + QLatin1String(".o5m"); +} + +void TileCache::downloadNext() +{ + if (m_output.isOpen() || m_pendingDownloads.empty()) { + return; + } + + const auto tile = m_pendingDownloads.front(); + m_pendingDownloads.pop_front(); + + QFileInfo fi(cachePath(tile)); + QDir().mkpath(fi.absolutePath()); + m_output.setFileName(fi.absoluteFilePath() + QLatin1String(".part")); + if (!m_output.open(QFile::WriteOnly)) { + qWarning() << m_output.fileName() << m_output.errorString(); + return; + } QUrl url; url.setScheme(QStringLiteral("https")); url.setHost(QStringLiteral("maps.kde.org")); url.setPath(QLatin1String("/earth/vectorosm/v1/") + QString::number(tile.z) + QLatin1Char('/') + QString::number(tile.x) + QLatin1Char('/') + QString::number(tile.y) + QLatin1String(".o5m")); - // TODO stream incoming data to the final destination (or a file with a .part suffix to avoid reads while downlaoding) - QNetworkRequest req(url); auto reply = m_nam->get(req); - qDebug() << reply << url; - connect(reply, &QNetworkReply::finished, this, [this, reply, tile]() { - reply->deleteLater(); - qDebug() << reply->errorString() << reply->url(); - - QFileInfo fi(cachePath(tile)); - QDir().mkpath(fi.absolutePath()); - QFile f(fi.absoluteFilePath()); - if (!f.open(QFile::WriteOnly)) { - qWarning() << f.fileName() << f.errorString(); - return; - } - f.write(reply->readAll()); - }); + connect(reply, &QNetworkReply::readyRead, this, [this, reply]() { dataReceived(reply); }); + connect(reply, &QNetworkReply::finished, this, [this, reply, tile]() { downloadFinished(reply, tile); }); } -QString TileCache::cachePath(Tile tile) const +void TileCache::dataReceived(QNetworkReply *reply) { - return QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) - + QLatin1String("/org.kde.osm/vectorosm/") - + QString::number(tile.z) + QLatin1Char('/') - + QString::number(tile.x) + QLatin1Char('/') - + QString::number(tile.y) + QLatin1String(".o5m"); + m_output.write(reply->read(reply->bytesAvailable())); +} + +void TileCache::downloadFinished(QNetworkReply* reply, Tile tile) +{ + reply->deleteLater(); + m_output.close(); + + if (reply->error() != QNetworkReply::NoError) { + qWarning() << reply->errorString() << reply->url(); + m_output.remove(); + downloadNext(); + return; + } + + m_output.close(); + m_output.rename(cachePath(tile)); + + Q_EMIT tileLoaded(tile); + downloadNext(); +} + +int TileCache::pendingDownloads() const +{ + return m_pendingDownloads.size() + (m_output.isOpen() ? 1 : 0); } diff --git a/src/map/loader/tilecache.h b/src/map/loader/tilecache.h index d7461b5..160cf3c 100644 --- a/src/map/loader/tilecache.h +++ b/src/map/loader/tilecache.h @@ -1,62 +1,80 @@ /* Copyright (C) 2020 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef KOSMINDOORMAP_TILECACHE_H #define KOSMINDOORMAP_TILECACHE_H +#include #include +#include + class QNetworkAccessManager; +class QNetworkReply; namespace KOSMIndoorMap { /** Identifier of a slippy map tile. * @see https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames */ class Tile { public: static Tile fromCoordinate(double lat, double lon, uint8_t z); uint32_t x = 0; uint32_t y = 0; uint8_t z = 0; }; /** OSM vector tile downloading and cache management. */ class TileCache : public QObject { Q_OBJECT public: explicit TileCache(QObject *parent = nullptr); ~TileCache(); /** Returns the path to the cached content of @p tile, if present locally. */ QString cachedTile(Tile tile) const; + /** Ensure @p tile is locally cached. */ + void ensureCached(Tile tile); + /** Triggers the download of tile @p tile. */ void downloadTile(Tile tile); + /** Number of pending downloads. */ + int pendingDownloads() const; + +Q_SIGNALS: + void tileLoaded(Tile tile); + private: QString cachePath(Tile tile) const; + void downloadNext(); + void dataReceived(QNetworkReply *reply); + void downloadFinished(QNetworkReply *reply, Tile tile); QNetworkAccessManager *m_nam; + QFile m_output; + std::deque m_pendingDownloads; }; } #endif // KOSMINDOORMAP_TILECACHE_H